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