/** * @param array $post_fields Associative array of fields posted to listener * @return bool */ function validate($post_fields = array()) { $url = Configuration::getDefaultConfig()->val('postback-url'); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); // TODO we can put VERIFIED in config and generalize this // Always capture the cURL output $curlDebugLog = fopen('php://temp', 'r+'); curl_setopt($ch, CURLOPT_VERBOSE, true); curl_setopt($ch, CURLOPT_STDERR, $curlDebugLog); $response = $this->curl($ch, $post_fields); // Read the logging output rewind($curlDebugLog); $logged = fread($curlDebugLog, 8192); fclose($curlDebugLog); Logger::debug("cURL verbose logging: {$logged}"); if ($response === 'VERIFIED') { return true; } elseif ($response === 'INVALID') { return false; } else { // TODO: Log txn_id. This is annoying because of the random document formats. Logger::debug("Unknown response from PayPal IPN PB: [{$response}].\n" . "Verbose logging: {$logged}"); // FIXME: The same thing happens for "INVALID" and totally broken // responses. Differentiate. return false; } }
/** * Instantiates and runs a job defined by a queue message. Depends on * the base consumer's damaged message store functionality to either * divert messages or stop execution on bad message or job failure. * @param array $jobMessage * @throws \SmashPig\Core\DataStores\DataSerializationException */ function processMessage($jobMessage) { if (!isset($jobMessage['php-message-class'])) { throw new RuntimeException('Job message missing required key \'php-message-class\''); } // TODO: encapsulate the reconstitution step elsewhere. // FIXME It seems bad that these objects indiscriminately store // things as properties. The message is mingled with stuff like // php-message-class. Could collide. $className = $jobMessage['php-message-class']; Logger::info("Hydrating a message with class {$className}"); $jsonMessage = json_encode($jobMessage); Logger::debug("Job payload: {$jsonMessage}"); $jobObj = KeyedOpaqueStorableObject::fromJsonProxy($className, $jsonMessage); if ($jobObj instanceof Runnable) { Logger::info('Running job'); if (!$jobObj->execute()) { throw new RuntimeException('Job tells us that it did not successfully execute. ' . 'Sending to damaged message store.'); } } else { // We don't know how to run this job type. throw new RuntimeException(get_class($jobObj) . ' is not an instance of Runnable. ' . 'Could not execute and sending to damaged message store.'); } $this->successCount += 1; }
/** * Do the actual work of the script. */ public function execute() { Logger::info('Info log message'); Logger::debug('Debug log message'); Logger::notice('Notice...'); Logger::getTaggedLogger('RawData')->info('This should be tagged RawData'); Logger::warning('Warning!', array('foo' => 'bar')); try { $this->throwException(); } catch (SmashPigException $ex) { Logger::error('ERROR!!!!', null, $ex); } }
protected function downloadReport($reportInfo) { $id = $reportInfo['ReportId']; // Remove common prefix from report type $type = str_replace('_GET_FLAT_FILE_OFFAMAZONPAYMENTS_', '', $reportInfo['ReportType']); if (array_search($id, $this->downloadedIds) === false) { Logger::debug("Downloading report dated {$reportInfo['AvailableDate']} with id: {$id}"); $report = $this->reportsClient->getReport(array('report_id' => $id)); $date = substr($reportInfo['AvailableDate'], 0, 10); $path = "{$this->downloadPath}/{$date}-{$type}{$id}.csv"; Logger::info("Saving report to {$path}"); file_put_contents($path, $report['ResponseBody']); } else { Logger::debug("Skipping downloaded report with id: {$id}"); } }
/** * @param WSDL\sendNotification $var * * @return WSDL\sendNotificationResponse */ public function sendNotification(WSDL\sendNotification $var) { $messages = array(); $respstring = "[failed]"; if ($var->notification instanceof WSDL\NotificationRequest) { if ($var->notification->live) { Logger::info("Notification received from live server."); } else { Logger::info("Notification received from test server."); } // Create Messages from the hideous SOAPy mess if (is_array($var->notification->notificationItems->NotificationRequestItem)) { foreach ($var->notification->notificationItems->NotificationRequestItem as $item) { $obj = $this->createAdyenMsgObjFromItem($item); if ($obj !== false) { $messages[] = $obj; } } } else { $obj = $this->createAdyenMsgObjFromItem($var->notification->notificationItems->NotificationRequestItem); if ($obj !== false) { $messages[] = $obj; } } $numItems = count($messages); Logger::info("Extracted {$numItems} from received message. Beginning processing loop."); // Now process each message to the best of our ability foreach ($messages as $msg) { if ($this->processMessage($msg)) { Logger::debug("Message successfully processed. Moving along..."); } else { Logger::error("Message was not successfully processed!", $msg); } } Logger::info('Finished processing of IPN message, retuning accepted.'); $respstring = '[accepted]'; } else { Logger::warning("Received notification is not instance of NotificationRequest!", $var); $this->server->fault(500, 'Received notification is not instance of NotificationRequest!'); } $response = new WSDL\sendNotificationResponse(); $response->notificationResponse = $respstring; return $response; }
public function __construct($path) { $this->basePath = $path; $this->objectsPath = $this->basePath . '/objects'; $this->keysPath = $this->basePath . '/keys'; Logger::debug("Constructing DiskFileStore with path {$this->basePath}"); if (!file_exists($this->basePath) && !mkdir($this->basePath, 0770, true)) { Logger::info("Could not create base store directory {$this->basePath}"); throw new DataStoreException("Could not create writeable directory: '{$this->basePath}'"); } if (!file_exists($this->objectsPath) && !mkdir($this->objectsPath, 0770, true)) { Logger::info("Could not create object store directory {$this->objectsPath}"); throw new DataStoreException("Could not create writeable directory: '{$this->objectsPath}'"); } if (!file_exists($this->keysPath) && !mkdir($this->keysPath, 0770, true)) { Logger::info("Could not create key links store directory {$this->keysPath}"); throw new DataStoreException("Could not create writeable directory: '{$this->keysPath}'"); } }
/** * Creates an appropriate derived AdyenMessage instance from the object received * during the SOAP transaction. * * The magic here is looking at the eventCode field, normalizing it, and then * loading the class if it exists. * * @param \SmashPig\PaymentProviders\Adyen\WSDL\NotificationRequestItem $obj */ public static function getInstanceFromWSDL(NotificationRequestItem $msgObj) { // Adyen events come in as UPPER_CASE_UNDERSCORE_DELIMITED, we turn this // into UpperCaseUnderscoreDelimited $className = implode('', array_map('ucwords', explode('_', strtolower($msgObj->eventCode)))); $className = 'SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\' . $className; if (class_exists($className)) { Logger::debug("Attempting construction of '{$className}'"); $obj = new $className(); } else { Logger::debug("Class not found '{$className}'"); return false; } if ($obj instanceof AdyenMessage) { $obj->constructFromWSDL($msgObj); } else { throw new ListenerDataException("Instantiated object '{$className}' does not inherit from AdyenMessage'!"); } return $obj; }
/** * Will run all the actions that are loaded (from the 'actions' configuration * node) and that are applicable to this message type. Will return true * if all actions returned true. Otherwise will return false. This implicitly * means that the message will be re-queued if any action fails. Therefore * all actions need to be idempotent. * * @returns bool True if all actions were successful. False otherwise. */ public function runActionChain() { $retval = true; // TODO: Cache this? $actions = Context::get()->getConfiguration()->val('actions'); foreach ($actions as $actionClassName) { $action = new $actionClassName(); if ($action instanceof IListenerMessageAction) { Logger::debug("Running action {$actionClassName}."); if (!$action->execute($this)) { Logger::info("Action {$actionClassName} did not execute properly, will re-queue."); $retval = false; break; } else { Logger::debug("Action returned success."); } } else { Logger::error("Entry under actions node '{$actionClassName}' does not implement IListenerActionMessage"); } } return $retval; }
protected function createIpnMessages($pendingMessage, $templates, $outputDir) { $oid = $pendingMessage['order_id']; $replacements = array('[[CURRENCY]]' => $pendingMessage['currency'], '[[AMOUNT]]' => $pendingMessage['gross'], '[[AMOUNT_IN_CENTS]]' => floatval($pendingMessage['gross']) * 100, '[[ORDER_ID]]' => $oid, '[[PROCESSOR_REF_1]]' => mt_rand(), '[[PROCESSOR_REF_2]]' => mt_rand()); if ($this->getArgument('gateway') === 'astropay') { // ugly, but whatchagonnado? $replacements['[[ASTROPAY_SIGNATURE_SUCCESS]]'] = $this->getAstroPaySignature($pendingMessage, '9'); $replacements['[[ASTROPAY_SIGNATURE_FAILURE]]'] = $this->getAstroPaySignature($pendingMessage, '8'); } foreach ($templates as $template) { $fullPath = $this->templateDir . $template; if (is_dir($fullPath)) { continue; } $contents = file_get_contents($fullPath); $fname = $outputDir . '/' . preg_replace('/(.[a-z0-9]+)$/i', '.' . $oid . '\\1', $template); foreach ($replacements as $search => $replace) { $contents = str_replace($search, $replace, $contents); } file_put_contents($fname, $contents); Logger::debug("Wrote {$fname}."); } }
/** * @return Response */ public static function process() { // Can go away once we require PHP 5.6 ini_set('default_charset', 'UTF-8'); // --- Get the request and response objects $request = Request::createFromGlobals(); $response = new Response(); $response->setPrivate(); // --- Break the request into parts --- $uri = $request->query->get('p', ''); $parts = explode('/', $uri); $request->query->remove('p'); if (count($parts) < 2) { $response->setStatusCode(403, 'Cannot process this request: bad URI format. A configuration node and an action is required'); return $response; } $view = array_shift($parts); $action = array_shift($parts); // --- Initialize core services --- $config = Configuration::createForView($view); Context::init($config); Logger::init($config->val('logging/root-context'), $config->val('logging/log-level'), $config, Context::get()->getContextId()); if ($config->nodeExists('disabled') && $config->val('disabled')) { Logger::debug('403 will be given for disabled view.', $uri); $response->setStatusCode(403, "View '{$view}' disabled. Cannot continue."); return $response; } if ($config->nodeExists('charset')) { // recreate the request with a different input encoding // FIXME: This is only converting the POST values. Also, // is there really no better way to do this? $decoded = rawurldecode($request->getContent()); $content = mb_convert_encoding($decoded, 'UTF-8', $config->val('charset')); parse_str($content, $data); $request->request = new ParameterBag($data); } set_error_handler('\\SmashPig\\Core\\Http\\RequestHandler::lastChanceErrorHandler'); set_exception_handler('\\SmashPig\\Core\\Http\\RequestHandler::lastChanceExceptionHandler'); register_shutdown_function('\\SmashPig\\Core\\Http\\RequestHandler::shutdownHandler'); // Check to make sure there's even a point to continuing Logger::info("Starting processing for request, configuration view: '{$view}', action: '{$action}'"); if (!$config->nodeExists("endpoints/{$action}")) { Logger::debug('403 will be given for unknown action on inbound URL.', $uri); $response->setStatusCode(403, "Action '{$action}' not configured. Cannot continue."); return $response; } // Inform the request object of our security environment $trustedHeader = $config->val('security/ip-header-name'); if ($trustedHeader) { $request->setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeader); } $trustedProxies = $config->val('security/ip-trusted-proxies'); if ($trustedProxies) { $request->setTrustedProxies($trustedProxies); } // --- Actually get the endpoint object and start the request --- $endpointObj = $config->object("endpoints/{$action}"); if ($endpointObj instanceof IHttpActionHandler) { $endpointObj->execute($request, $response); } else { $str = "Requested action '{$action}' does not implement a known handler. Cannot continue."; Logger::debug($str); $response->setStatusCode(500, $str); } $code = $response->getStatusCode(); if ($code !== 200 && $code !== 302) { $response->setContent(''); } return $response; }
/** * Acknowledges and replaces, into the backing data store, the current queue message */ public function queueIgnoreObject() { if (!$this->queueMsg) { throw new DataStoreTransactionException("No STOMP transaction currently in progress. Cannot requeue a non-existent message!"); } Logger::debug("Acking STOMP message {$this->queueMsg->headers['message-id']}"); $this->stompObj->ack($this->queueMsg); Logger::debug("Re-adding STOMP message to queue"); $sent = $this->stompObj->send($this->queue_id, $this->queueMsg->body, $this->queueMsg->headers); if (!$sent) { Logger::error("Could not re-queue message to '{$this->queue_id}' on '{$this->uri}'", $this->queueMsg); throw new DataStoreException("Could not re-queue message to '{$this->queue_id}' on '{$this->uri}'"); } $this->queueMsg = null; Logger::info("STOMP message requeued from '{$this->queue_id}' on '{$this->uri}'"); }
public static function getQueue($queueName) { $config = Context::get()->getConfiguration(); $key = "data-store/{$queueName}"; Logger::debug("Getting queue {$queueName} from key {$key}"); // Get a reference to the config node so we can mess with it $node =& $config->val($key, true); if (empty($node['constructor-parameters']) || empty($node['constructor-parameters'][0]['queue'])) { Logger::debug("'queue' not set, defaulting to {$queueName}"); $node['constructor-parameters'][0]['queue'] = $queueName; } return $config->object($key, true); }