Adamw has submitted this change and it was merged.

Change subject: Process the Accounting Report File from Adyen
......................................................................


Process the Accounting Report File from Adyen

RIght now this only allows us to reprocess missed the missed transactions
that happen occassionally due to some race conditions in the job logic
(an IPN message would come in before the donor had returned in the middle
of job processing -- so no data would be found)

Still to do would be a good audit script that runs on the civi host to
ensure that everything is in civi -- and that also collides orphans :)

Change-Id: I588832e0fd2e2e7fc142c9c35e8c7cc9fabb2dc1
---
M PaymentProviders/Adyen/ExpatriatedMessages/AdyenMessage.php
M PaymentProviders/Adyen/ExpatriatedMessages/ReportAvailable.php
A PaymentProviders/Adyen/Jobs/ProcessAccountingReportJob.php
3 files changed, 193 insertions(+), 3 deletions(-)

Approvals:
  Adamw: Verified; Looks good to me, approved



diff --git a/PaymentProviders/Adyen/ExpatriatedMessages/AdyenMessage.php 
b/PaymentProviders/Adyen/ExpatriatedMessages/AdyenMessage.php
index 0b31560..4221935 100644
--- a/PaymentProviders/Adyen/ExpatriatedMessages/AdyenMessage.php
+++ b/PaymentProviders/Adyen/ExpatriatedMessages/AdyenMessage.php
@@ -33,6 +33,9 @@
         * necessarily mean that the request itself was successful. IE: refund 
request received but not yet accepted. */
        public $success;
 
+       /** @var string|null Reason for event */
+       public $reason;
+
        /**
         * Creates an appropriate derived AdyenMessage instance from the object 
received
         * during the SOAP transaction.
@@ -78,14 +81,17 @@
         * @param NotificationRequestItem $msgObj Received and processed object
         */
        protected function constructFromWSDL( NotificationRequestItem $msgObj ) 
{
-               $this->currency = $msgObj->amount->currency;
-               $this->amount = $msgObj->amount->value / 100;   // TODO: Make 
this CLDR aware
+               if ( $msgObj->amount ) {
+                       $this->currency = $msgObj->amount->currency;
+                       $this->amount = $msgObj->amount->value / 100;   // 
TODO: Make this CLDR aware
+               }
                $this->eventDate = $msgObj->eventDate;
                $this->merchantAccountCode = $msgObj->merchantAccountCode;
                $this->merchantReference = $msgObj->merchantReference;
                $this->parentPspReference = $msgObj->originalReference;
                $this->pspReference = $msgObj->pspReference;
                $this->success = (bool)$msgObj->success;
+               $this->reason = $msgObj->reason;
 
                $this->correlationId = static::createCorrelationId( 
$this->pspReference );
        }
diff --git a/PaymentProviders/Adyen/ExpatriatedMessages/ReportAvailable.php 
b/PaymentProviders/Adyen/ExpatriatedMessages/ReportAvailable.php
index 98d8960..bd67f0e 100644
--- a/PaymentProviders/Adyen/ExpatriatedMessages/ReportAvailable.php
+++ b/PaymentProviders/Adyen/ExpatriatedMessages/ReportAvailable.php
@@ -1,5 +1,38 @@
 <?php namespace SmashPig\PaymentProviders\Adyen\ExpatriatedMessages;
 
-class ReportAvailable extends AdyenMessage {
+use SmashPig\Core\Configuration;
+use SmashPig\Core\Logging\Logger;
+use SmashPig\PaymentProviders\Adyen\Jobs\ProcessAccountingReportJob;
 
+class ReportAvailable extends AdyenMessage {
+       /**
+        * 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() {
+               Logger::info(
+                       "Received new report from Adyen: {$this->pspReference}. 
Generated: {$this->eventDate}.",
+                       $this->reason
+               );
+
+               $jobQueueObj = Configuration::getDefaultConfig()->obj( 
'data-store/jobs' );
+               if ( strpos( $this->pspReference, 'payments_accounting_report' 
) === 0 ) {
+                       $jobQueueObj->addObject(
+                               ProcessAccountingReportJob::factory(
+                                       $this->merchantAccountCode,
+                                       $this->reason
+                               )
+                       );
+               } else {
+                       // We don't know how to handle this report yet
+                       Logger::notice( "Do not know how to handle report with 
name '{$this->pspReference}'" );
+               }
+
+               return parent::runActionChain();
+       }
 }
diff --git a/PaymentProviders/Adyen/Jobs/ProcessAccountingReportJob.php 
b/PaymentProviders/Adyen/Jobs/ProcessAccountingReportJob.php
new file mode 100644
index 0000000..e13743f
--- /dev/null
+++ b/PaymentProviders/Adyen/Jobs/ProcessAccountingReportJob.php
@@ -0,0 +1,151 @@
+<?php namespace SmashPig\PaymentProviders\Adyen\Jobs;
+
+use SmashPig\Core\AutoLoader;
+use SmashPig\Core\Configuration;
+use SmashPig\Core\DataFiles\HeadedCsvReader;
+use SmashPig\Core\Logging\TaggedLogger;
+use SmashPig\Core\SmashPigException;
+use SmashPig\Core\Jobs\RunnableJob;
+
+/**
+ * Process Adyen end of day payment reports. These reports are named
+ * payments_accounting_report_[yyyy]_[mm]_[dd].csv
+ *
+ * @package SmashPig\PaymentProviders\Adyen\Jobs
+ */
+class ProcessAccountingReportJob extends RunnableJob {
+
+       /** @var TaggedLogger */
+       protected $logger;
+
+       protected $account;
+       protected $reportUrl;
+
+       protected $downloadLoc;
+
+       public static function factory( $account, $url ) {
+               $obj = new ProcessAccountingReportJob();
+
+               $obj->account = $account;
+               $obj->reportUrl = $url;
+
+               return $obj;
+       }
+
+       public function execute() {
+               $this->logger = new TaggedLogger( __CLASS__ );
+               $c = Configuration::getDefaultConfig();
+
+               // Construct the temporary file path
+               $fileName = basename( $this->reportUrl );
+               $this->downloadLoc = AutoLoader::makePath(
+                       $c->val( 
"payment-provider/adyen/accounts/{$this->account}/report-location" ),
+                       end( $fileName )
+               );
+
+               // Actually get the file
+               $this->downloadLog();
+
+               // Now iterate through; finding all the ones marked Authorised 
and SentForSettle
+               // The Authorised entries at the end that do not have a 
SentForSettle need to be
+               // sent out again.
+               $this->logger->debug( "Now starting Authorized -> SentForSettle 
search loop" );
+               $f = new HeadedCsvReader( $this->downloadLoc );
+               $tempQueue = array();
+
+               foreach ( $f as $row ) {
+                       switch ( $f->extractCol( 'Record Type', $row ) ) {
+                               case 'Authorised':
+                                       $tempQueue[$f->extractCol( 'Psp 
Reference', $row )] = $row;
+                                       break;
+
+                               case 'SentForSettle':
+                                       unset( $tempQueue[$f->extractCol( 'Psp 
Reference', $row )] );
+                                       // TODO: Audit this row; probably send 
it to an audit queue
+                                       break;
+
+                               default:
+                                       // Don't care :)
+                                       break;
+                       }
+               }
+
+               $tqc = count( $tempQueue );
+               $this->logger->debug(
+                       "Of {$f->key()} rows, {$tqc} need to be reprocessed.",
+                       array( 'headers' => $f->headers(), 'values' => 
$tempQueue )
+               );
+
+               foreach ( $tempQueue as $pspRef => $row ) {
+                       $correlationId = 'adyen-' . $f->extractCol( 'Merchant 
Reference', $row );
+                       $this->logger->enterContext( "$correlationId" );
+
+                       $currency = $f->extractCol( 'Payment Currency', $row );
+                       $amount = floatval( $f->extractCol( 'Authorised (PC)', 
$row ) );
+                       $account = $f->extractCol( 'Merchant Account', $row );
+
+                       $this->logger->info(
+                               "Recreating Adyen capture job for {$currency} 
{$amount} with id {$correlationId} and " .
+                               "psp reference {$pspRef}."
+                       );
+                       $jobQueueObj = Configuration::getDefaultConfig()->obj( 
'data-store/jobs' );
+                       $jobQueueObj->addObject(
+                               ProcessCaptureRequestJob::factory(
+                                       $correlationId,
+                                       $account,
+                                       $currency,
+                                       $amount,
+                                       $pspRef
+                               )
+                       );
+
+                       $this->logger->leaveContext(); // Must be the last line 
in this loop
+               }
+
+               return true;
+       }
+
+       protected function downloadLog() {
+               $c = Configuration::getDefaultConfig();
+
+               $user = $c->val( 
"payment-provider/adyen/accounts/{$this->account}/report-username" );
+               $pass = $c->val( 
"payment-provider/adyen/accounts/{$this->account}/report-password" );
+
+               $this->logger->info(
+                       "Beginning log download from {$this->reportUrl} using 
username {$user} into {$this->downloadLoc}"
+               );
+
+               $fp = fopen( $this->downloadLoc, 'w' );
+               if ( !$fp ) {
+                       $str = "Could not open {$this->downloadLoc} for 
writing! Will not download/process report.";
+                       $this->logger->error( $str );
+                       throw new SmashPigException( $str );
+               }
+
+               $ch = curl_init();
+               curl_setopt($ch, CURLOPT_URL, $this->reportUrl);
+
+               curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
+               curl_setopt($ch, CURLOPT_FILE, $fp );
+
+               curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+
+               curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+               curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
+               curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
+               curl_setopt($ch, CURLOPT_USERPWD, "{$user}:{$pass}" );
+
+               $result = curl_exec($ch);
+               $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
+               $error = curl_error( $ch );
+               curl_close( $ch );
+
+               if ( $result === false ) {
+                       $this->logger->error( "Could not download report due to 
cURL error {$error}" );
+                       throw new SmashPigException( "Could not download 
report." );
+               } elseif ( $httpCode !== 200 ) {
+                       $this->logger->error( "Report downloaded(?), but with 
incorrect HTTP code: {$httpCode}" );
+                       throw new SmashPigException( "Could not download 
report." );
+               }
+       }
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/90285
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I588832e0fd2e2e7fc142c9c35e8c7cc9fabb2dc1
Gerrit-PatchSet: 3
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Mwalker <[email protected]>
Gerrit-Reviewer: Adamw <[email protected]>
Gerrit-Reviewer: Katie Horn <[email protected]>
Gerrit-Reviewer: Mwalker <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to