public function setUp() { parent::setUp(); $this->config = AdyenTestConfiguration::createWithSuccessfulApi(); Context::initWithLogger($this->config); $this->jobQueue = $this->config->object('data-store/jobs-adyen'); $this->jobQueue->createTable('jobs-adyen'); }
public function execute() { $logger = Logger::getTaggedLogger("corr_id-adyen-{$this->merchantReference}"); $logger->info("Recording successful capture on account '{$this->account}' with authorization reference " . "'{$this->originalReference}' and order ID '{$this->merchantReference}'."); $config = Configuration::getDefaultConfig(); // Find the details from the payment site in the pending database. $logger->debug('Attempting to locate associated message in pending database'); $db = PendingDatabase::get(); $dbMessage = $db->fetchMessageByGatewayOrderId('adyen', $this->merchantReference); if ($dbMessage && isset($dbMessage['order_id'])) { $logger->debug('A valid message was obtained from the pending queue'); // Add the gateway transaction ID and send it to the completed queue $dbMessage['gateway_txn_id'] = $this->originalReference; $queueMessage = DonationInterfaceMessage::fromValues($dbMessage); SourceFields::addToMessage($queueMessage); $config->object('data-store/verified')->push($queueMessage); // Remove it from the pending database $logger->debug('Removing donor details message from pending database'); $db->deleteMessage($dbMessage); } else { // Sometimes we don't have a pending db row because the donor made // multiple attempts with the same order ID. It would be nice if // Adyen could prevent that, but let's not send a failmail since // we'll eventually get the donor details from the payments log // when we parse the audit. $logger->warning("Could not find donor details for authorization Reference '{$this->originalReference}' " . "and order ID '{$this->merchantReference}'.", $dbMessage); } return true; }
/** * @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; } }
public static function installTestConfiguration($pathOverrides = array()) { // Late static binding so that a subclass creates one of itself $singleton = static::createForViewWithOverrideFile('default', $pathOverrides); Configuration::$defaultObj = $singleton; return $singleton; }
protected function getAstroPaySignature($pendingMessage, $result) { $c = Configuration::getDefaultConfig(); $login = $c->val('login'); $secret = $c->val('secret'); $signed = $login . $result . $pendingMessage['gross'] . $pendingMessage['order_id']; return strtoupper(hash_hmac('sha256', pack('A*', $signed), pack('A*', $secret))); }
/** * MultiQueueWriter constructor. * * @param array $backends list of config keys under data-store */ public function __construct($backends) { $config = Configuration::getDefaultConfig(); foreach ($backends as $configKey) { $path = 'data-store/' . $configKey; $this->queues[] = $config->object($path); } }
public function testConsume() { foreach (self::$messages as $msg) { $this->capture($msg['payload']); $jobQueue = $this->config->object('data-store/jobs-paypal'); $jobMessage = $jobQueue->pop(); $job = KeyedOpaqueStorableObject::fromJsonProxy($jobMessage['php-message-class'], json_encode($jobMessage)); $job->execute(); $queue = $this->config->object('data-store/' . $msg['type']); $queue->createTable($msg['type']); $message = $queue->pop(); if ($job->is_reject()) { $this->assertEmpty($message); } else { $this->assertNotEmpty($message); if (isset($message['contribution_tracking_id'])) { $this->assertEquals($message['contribution_tracking_id'], $message['order_id']); } } } }
/** * Initialize the logging framework. * * Will add all LogStreams registered under logging/enabled-log-streams. These * handlers must have class instantiation entries under logging/log-streams/<name>. * * @param string $name Root context name * @param int $threshold Minimum log level to record into the context * @param Configuration $config Configuration object to use * @param string $prefix Base prefix for logger */ static function init($name, $threshold, Configuration $config, $prefix) { if (self::$context) { // FIXME: is this necessary? throw new SmashPigException("Attempting to reinitialize the logger is not allowed!"); } // Init all the log streams $streamObjs = array(); try { $streams = $config->val('logging/enabled-log-streams'); foreach ($streams as $streamName) { $streamObjs[] = $config->object("logging/log-streams/{$streamName}", false); } } catch (\Exception $ex) { trigger_error("Exception while creating default log streams: {$ex->getMessage()} at {$ex->getTraceAsString()}", E_USER_ERROR); die; } self::$context = new LogContextHandler($name, $streamObjs); self::$context->enterContext($prefix); self::$threshold = $threshold; }
public function execute(ListenerMessage $msg) { $tl = new TaggedLogger('CaptureResponseAction'); if ($msg instanceof Capture) { if ($msg->success) { $tl->info("Adding record capture job for {$msg->currency} {$msg->amount} with id {$msg->correlationId} and psp reference {$msg->pspReference}."); $recordJob = RecordCaptureJob::factory($msg); $jobQueue = Configuration::getDefaultConfig()->object('data-store/jobs-adyen'); $jobQueue->push(json_decode($recordJob->toJson(), true)); } else { $tl->warning("Capture failed for payment with reference {$msg->pspReference} and correlation id {$msg->correlationId}.", $msg); } } return true; }
/** * When two authorizations come in with the same merchant reference, we * should cancel the second one and leave the donor details in pending. */ public function testDuplicateAuthorisation() { $api = $this->config->object('payment-provider/adyen/api', true); $auth1 = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Authorisation', file_get_contents(__DIR__ . '/../Data/auth.json')); $job1 = ProcessCaptureRequestJob::factory($auth1); $job1->execute(); $this->assertEquals(1, count($api->captured), 'Set up failed'); $auth2 = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Authorisation', file_get_contents(__DIR__ . '/../Data/auth.json')); $auth2->pspReference = mt_rand(1000000000, 10000000000); $job2 = ProcessCaptureRequestJob::factory($auth2); $this->assertTrue($job2->execute(), 'Duplicate auths should not clutter damage queue'); $this->assertEquals(1, count($api->captured), 'Captured a duplicate!'); $this->assertEquals($auth2->pspReference, $api->cancelled[0], 'Did not cancel the right authorization'); $this->assertNotNull($this->pendingDatabase->fetchMessageByGatewayOrderId('adyen', $auth1->merchantReference), 'Capture job should leave donor details in database'); }
public function execute(ListenerMessage $msg) { $tl = new TaggedLogger('PaymentCaptureAction'); if ($msg instanceof Authorisation) { $jobQueueObj = Configuration::getDefaultConfig()->object('data-store/jobs-adyen'); if ($msg->success) { // Here we need to capture the payment, the job runner will collect the // orphan message $tl->info("Adding Adyen capture job for {$msg->currency} {$msg->amount} " . "with id {$msg->correlationId} and psp reference {$msg->pspReference}."); $job = ProcessCaptureRequestJob::factory($msg); $jobQueueObj->push(json_decode($job->toJson(), true)); } else { // And here we just need to destroy the orphan $tl->info("Adyen payment with correlation id {$msg->correlationId} " . "reported status failed: '{$msg->reason}'. " . 'Queueing job to delete pending records.'); $job = DeletePendingJob::factory('adyen', $msg->merchantReference, $msg->correlationId); $jobQueueObj->push(json_decode($job->toJson(), true)); } } return true; }
public function execute(Request $request, Response $response) { $this->config = Configuration::getDefaultConfig(); $requestValues = $request->getValues(); // Don't store blank messages. if (empty($requestValues)) { return; } // Don't store invalid messages. $valid = $this->config->object('api')->validate($requestValues); if (!$valid) { // This will tell them to resend later. $response->setStatusCode(403, 'Failed verification'); return false; } // Dump the request right into the queue with no validation. $job = new Job(); $job->payload = $requestValues; $this->config->object('data-store/jobs-paypal')->push($job); Logger::info('Pushed new message to jobs-paypal: ' . print_r($requestValues, true)); }
/** * Do the actual work of the script. */ public function execute() { $this->damagedDatabase = DamagedDatabase::get(); $messages = $this->damagedDatabase->fetchRetryMessages($this->getOption('max-messages')); $stats = array(); $config = Configuration::getDefaultConfig(); foreach ($messages as $message) { $queueName = $message['original_queue']; // FIXME: getting it by alias, this will be annoying cos -new $queue = BaseQueueConsumer::getQueue($queueName); unset($message['original_queue']); $queue->push($message); $this->damagedDatabase->deleteMessage($message); if (isset($stats[$queueName])) { $stats[$queueName]++; } else { $stats[$queueName] = 1; } } foreach ($stats as $queueName => $count) { Logger::info("Requeued {$count} messages to {$queueName}."); } }
public function execute() { $this->config = Configuration::getDefaultConfig(); if ($this->is_reject()) { // Returning false would cause it to go to the damaged queue, we // just want to forget about these. return true; } // XXX Why does everything get made into objects? $request = (array) $this->payload; // Determine message type. if (isset($request['txn_type'])) { $txn_type = $request['txn_type']; } elseif (isset($request['payment_status']) && in_array($request['payment_status'], array('Reversed', 'Refunded'))) { // refund, chargeback, or reversal $txn_type = 'refund'; } else { throw new Exception('Invalid PayPal message: ' . json_encode($request)); } $msg_type = null; foreach ($this->config->val('messages') as $type => $conf) { if (in_array($txn_type, $conf['txn_types'])) { $msg_type = $type; } } if (!$msg_type) { throw new Exception('Invalid PayPal message type: ' . $txn_type); } // Transform into new message. // FIXME this could just be an array, but we need compat with // keyedopaque* until activemq goes away $new_msg = new Message(); // FIXME hacks because the recurring consumer doesn't want // a normalized message if ($msg_type === 'recurring') { foreach ($request as $key => $val) { $new_msg->{$key} = $val; } } else { $map = $this->config->val('var_map'); foreach ($map as $rx => $tx) { if (array_key_exists($rx, $request)) { $new_msg->{$tx} = $request[$rx]; } } // FIXME: var map can't put one thing in two places if (isset($new_msg->contribution_tracking_id)) { $new_msg->order_id = $new_msg->contribution_tracking_id; } // FIXME represent special case as var_map config override? if ($msg_type === 'refund') { $new_msg->gateway_refund_id = $request['txn_id']; $new_msg->gross_currency = $request['mc_currency']; if (isset($new_msg->type) && $new_msg->type === 'chargeback_settlement') { $new_msg->type = 'chargeback'; } else { $new_msg->type = $msg_type; } } // FIXME once recurring uses normalized msg it needs this too $new_msg->date = strtotime($new_msg->date); } $new_msg->gateway = 'paypal'; SourceFields::addToMessage($new_msg); // Save to appropriate queue. $this->config->object('data-store/' . $msg_type)->push($new_msg); // FIXME random document formats if (substr($txn_type, 0, 7) === 'subscr_') { $log_id = "subscr_id:{$request['subscr_id']}"; } else { $log_id = "txn_id:{$request['txn_id']}"; } Logger::info("Message {$log_id} pushed to {$msg_type} queue."); // TODO It would be nice if push() returned something useful so we // could return something here too return true; }
/** * Gets the configuration object associated with the current context. * * Set the configuration using init() * * Use this instead of Configuration::getDefaultConfig(); * * @return null|Configuration */ public function getConfiguration() { if ($this->config) { return $this->config; } else { Logger::notice('Context returning default configuration. Probably missing a setConfiguration().', debug_backtrace(null)); return Configuration::getDefaultConfig(); } }
/** * @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; }
/** * @return \SmashPig\PaymentProviders\Adyen\AdyenPaymentsInterface */ protected function getApi() { $api = Configuration::getDefaultConfig()->object('payment-provider/adyen/api'); $api->setAccount($this->account); return $api; }
/** * Set a test configuration and initialize the context * * @param string $configNode node to use for configuration overrides * @param string $configPath path to configuration override file * @return Configuration */ function setConfig($configNode = 'default', $configPath = null) { $config = Configuration::createForViewWithOverrideFile($configNode, $configPath); Context::initWithLogger($config); return $config; }
/** * Do some sanity checking and framework setup */ public function setup() { global $maintClass; // Abort if called from a web server if (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) { $this->error('This script must be run from the command line', true); } if (version_compare(phpversion(), '5.2.4') >= 0) { // Send PHP warnings and errors to stderr instead of stdout. // This aids in diagnosing problems, while keeping messages // out of redirected output. if (ini_get('display_errors')) { ini_set('display_errors', 'stderr'); } // Don't touch the setting on earlier versions of PHP, // as setting it would disable output if you'd wanted it. // Note that exceptions are also sent to stderr when // command-line mode is on, regardless of PHP version. } // Set max execution time to 0 (no limit). PHP.net says that // "When running PHP from the command line the default setting is 0." // But sometimes this doesn't seem to be the case. ini_set('max_execution_time', 0); $this->loadParamsAndArgs(); $this->helpIfRequested(); $this->adjustMemoryLimit(); // --- Initialize core services --- $configNode = $this->getOption('config-node'); $configFile = $this->getOption('config-file'); $config = Configuration::createForViewWithOverrideFile($configNode, $configFile); Context::init($config); Logger::init($config->val('logging/root-context') . '-' . end(explode("\\", $maintClass)), $config->val('logging/log-level'), $config, Context::get()->getContextId()); Logger::getContext()->addLogStream(new ConsoleLogStream()); set_error_handler('\\SmashPig\\Maintenance\\MaintenanceBase::lastChanceErrorHandler'); set_exception_handler('\\SmashPig\\Maintenance\\MaintenanceBase::lastChanceExceptionHandler'); }
public static function instance() { return Configuration::createForViewWithOverrideFile('default', __DIR__ . '/data/config_smashpig_db.yaml'); }
/** * Using an AtomicReadBuffer implementation for the backend means that * if this throws an exception, the message will remain on the queue. * * @param array $message * @param Exception $ex */ protected function handleError($message, Exception $ex) { if ($ex instanceof RetryableException) { $now = UtcDate::getUtcTimestamp(); if (!isset($message['source_enqueued_time'])) { $message['source_enqueued_time'] = UtcDate::getUtcTimestamp(); } $expirationDate = $message['source_enqueued_time'] + Configuration::getDefaultConfig()->val('requeue-max-age'); if ($now < $expirationDate) { $retryDate = $now + Configuration::getDefaultConfig()->val('requeue-delay'); $this->sendToDamagedStore($message, $ex, $retryDate); return; } } $this->sendToDamagedStore($message, $ex); }