public function processMessage($message)
 {
     $logIdentifier = "message with gateway {$message['gateway']}" . " and order ID {$message['order_id']}";
     if ($this->paymentsInitialDatabase->isTransactionFailed($message)) {
         // Throw the message out if it's already failed
         Logger::info("Skipping failed {$logIdentifier}");
     } else {
         Logger::info("Storing {$logIdentifier} in database");
         $this->pendingDatabase->storeMessage($message);
     }
 }
 /**
  * We refuse to consume a message and drop it if the corresponding
  * payments_initial row is failed.
  */
 public function testPendingMessageInitialFailed()
 {
     $initRow = PaymentsInitialDatabaseTest::generateTestMessage();
     $initRow['payments_final_status'] = 'failed';
     $initRow['validation_action'] = 'reject';
     $this->paymentsInitialDb->storeMessage($initRow);
     $message = self::generatePendingMessageFromInitial($initRow);
     $consumer = new PendingQueueConsumer('pending', 1000, 1000);
     $consumer->processMessage($message);
     $fetched = $this->pendingDb->fetchMessageByGatewayOrderId($message['gateway'], $message['order_id']);
     $this->assertNull($fetched, 'Message consumed and not stored in the pending database.');
 }
 public function execute()
 {
     $this->logger = Logger::getTaggedLogger("corr_id-{$this->correlationId}");
     $this->logger->info("Running capture request job on account '{$this->account}' with reference '{$this->pspReference}' " . "and correlation id '{$this->correlationId}'.");
     // Determine if a message exists in the pending database; if it does not then
     // this payment has already been sent to the verified queue, or there is a
     // problem with the database. If it does exist, we need to check
     // $capture_requested in case we have requested a capture but have not yet
     // received notification of capture success. Either case can occur when a
     // donor submits their credit card details multiple times against a single
     // order ID. We should cancel duplicate authorizations, but leave payments
     // with missing donor details open for potential manual capture.
     $this->logger->debug('Attempting to locate associated message in pending database.');
     $db = PendingDatabase::get();
     $dbMessage = $db->fetchMessageByGatewayOrderId('adyen', $this->merchantReference);
     $success = true;
     $action = $this->determineAction($dbMessage);
     switch ($action) {
         case self::ACTION_PROCESS:
             // Attempt to capture the payment
             /**
              * @var AdyenPaymentsInterface
              */
             $api = $this->getApi();
             $this->logger->info("Attempting capture API call for currency '{$this->currency}', " . "amount '{$this->amount}', reference '{$this->pspReference}'.");
             $captureResult = $api->capture($this->currency, $this->amount, $this->pspReference);
             if ($captureResult) {
                 // Success!
                 $this->logger->info("Successfully captured payment! Returned reference: '{$captureResult}'. " . 'Marking pending database message as captured.');
                 $dbMessage['captured'] = true;
                 $db->storeMessage($dbMessage);
             } else {
                 // Some kind of error in the request. We should keep the pending
                 // db entry, complain loudly, and move this capture job to the
                 // damaged queue.
                 $this->logger->error("Failed to capture payment on account '{$this->account}' with reference " . "'{$this->pspReference}' and order id '{$this->merchantReference}'.", $dbMessage);
                 $success = false;
             }
             break;
         case self::ACTION_REJECT:
             $this->cancelAuthorization();
             // Delete the fraudy donor details
             $db->deleteMessage($dbMessage);
             break;
         case self::ACTION_DUPLICATE:
             // We have already captured one payment for this donation attempt, so
             // cancel the duplicate authorization. If there is a pending db entry,
             // leave it intact for the legitimate RecordCaptureJob.
             $this->cancelAuthorization();
             break;
         case self::ACTION_REVIEW:
             // Don't capture the payment right now, but leave the donor details in
             // the pending database in case the authorization is captured via the console.
             break;
         case self::ACTION_MISSING:
             // Missing donor details - retry later
             throw new RetryableException('Missing donor details');
     }
     return $success;
 }
 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;
 }
 public static function instance($overrides = array())
 {
     $config = self::createForViewWithOverrideFile('adyen', __DIR__ . '/config_test.yaml');
     $config->override($overrides);
     // FIXME: What is this doing here?
     PendingDatabase::get()->createTable();
     return $config;
 }
 public function execute()
 {
     $logger = Logger::getTaggedLogger("corr_id-{$this->gateway}-{$this->order_id}");
     $logger->info("Deleting message from pending db where gateway = '{$this->gateway}' " . "and order ID='{$this->order_id}'");
     $deleteParams = array('gateway' => $this->gateway, 'order_id' => $this->order_id);
     PendingDatabase::get()->deleteMessage($deleteParams);
     return true;
 }
 public function setUp()
 {
     parent::setUp();
     $this->setMwGlobals(array('wgDonationInterfaceOrphanCron' => array('enable' => true, 'target_execute_time' => self::TARGET_EXECUTE_TIME, 'time_buffer' => self::TIME_BUFFER), 'wgGlobalCollectGatewayEnabled' => true, 'wgDonationInterfaceGatewayAdapters' => array('globalcollect' => 'TestingGlobalCollectOrphanAdapter', 'globalcollect_orphan' => 'TestingGlobalCollectOrphanAdapter')));
     $config = SmashPigDatabaseTestConfiguration::instance();
     Context::init($config);
     $this->pendingDb = PendingDatabase::get();
     // Create the schema.
     $this->pendingDb->createTable();
 }
 public function testRecordCapture()
 {
     $verifiedQueue = BaseQueueConsumer::getQueue('verified');
     $verifiedQueue->createTable('verified');
     $capture = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Capture', file_get_contents(__DIR__ . '/../Data/capture.json'));
     $job = RecordCaptureJob::factory($capture);
     $this->assertTrue($job->execute());
     $donorData = $this->pendingDatabase->fetchMessageByGatewayOrderId('adyen', $capture->merchantReference);
     $this->assertNull($donorData, 'RecordCaptureJob left donor data on pending queue');
     $verifiedMessage = $verifiedQueue->pop();
     $this->assertNotNull($verifiedMessage, 'RecordCaptureJob did not send verified message');
     // can we use arraySubset yet?
     $sameKeys = array_intersect(array_keys($verifiedMessage), array_keys($this->pendingMessage));
     foreach ($sameKeys as $key) {
         if ($key === 'gateway_txn_id') {
             $this->assertEquals($capture->originalReference, $verifiedMessage[$key], 'RecordCaptureJob should have set gateway_txn_id');
         } else {
             $this->assertEquals($this->pendingMessage[$key], $verifiedMessage[$key], "Value of key {$key} mutated");
         }
     }
 }
 /**
  * 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');
 }
 /**
  * Do the actual work of the script.
  */
 public function execute()
 {
     $pendingDatabase = PendingDatabase::get();
     $numCreated = 0;
     $limit = $this->getOptionOrConfig('max-messages', 'maintenance/create-ipns/message-limit');
     $output = $this->getOption('output-dir');
     $gateway = $this->getArgument('gateway');
     $pendingMessages = $pendingDatabase->fetchMessagesByGatewayNewest($gateway, $limit);
     if (!$pendingMessages) {
         Logger::info("No pending database entries found for {$gateway}");
         return;
     }
     $this->templateDir = __DIR__ . '/../Tests/IPNTemplates/' . $gateway . '/';
     $templates = scandir($this->templateDir);
     foreach ($pendingMessages as $pendingMessage) {
         $this->createIpnMessages($pendingMessage, $templates, $output);
         $numCreated++;
     }
     Logger::info("Created {$numCreated} (sets of) IPN messages.");
 }
 public function testDeleteMessage()
 {
     $uniq = mt_rand();
     $message1 = $this->getTestMessage($uniq);
     // Store a second message for a good time, and make sure we delete the
     // right one.
     $message2 = $this->getTestMessage($uniq);
     $this->db->storeMessage($message1);
     $this->db->storeMessage($message2);
     // Confirm work without using the API.
     $pdo = $this->db->getDatabase();
     $result = $pdo->query("\n\t\t\tselect * from pending\n\t\t\twhere gateway='test'\n\t\t\t\tand order_id = '{$message1['order_id']}'");
     $rows = $result->fetchAll(PDO::FETCH_ASSOC);
     $this->assertEquals(2, count($rows), 'Both records were stored.');
     $this->assertNotNull($rows[0]['id'], 'Record includes a primary row id');
     $this->assertNotEquals($rows[0]['id'], $rows[1]['id'], 'Records have unique primary ids');
     $this->db->deleteMessage($message1);
     // Confirm work without using the API.
     $pdo = $this->db->getDatabase();
     $result = $pdo->query("\n\t\t\tselect * from pending\n\t\t\twhere gateway = 'test'\n\t\t\t\tand order_id = '{$message1['order_id']}'");
     $rows = $result->fetchAll(PDO::FETCH_ASSOC);
     $this->assertEquals(0, count($rows), 'All rows deleted.');
 }
 public function testDifferentDatabases()
 {
     $pendingPdo = $this->pendingDb->getDatabase();
     $initPdo = $this->paymentsInitialDb->getDatabase();
     $this->assertNotEquals(spl_object_hash($pendingPdo), spl_object_hash($initPdo), 'Pending and paymentsInit databases share the same PDO');
 }
 /**
  * Remove a message from the pending database.
  *
  * @param array Normalized message
  */
 protected function deleteMessage($message)
 {
     PendingDatabase::get()->deleteMessage($message);
 }