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);
 }