jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/368948 )

Change subject: Ingenico WX audit parsing
......................................................................


Ingenico WX audit parsing

Code from I1eed97f975617706e9 modified and reformatted to match SmashPig.

Splits out the payment product ID mapping into a static ReferenceData
class as with Adyen and AstroPay.

Bug: T86090
Change-Id: Ie85c11fca0f4155359361119420fca6325f5bb9b
---
A PaymentProviders/Ingenico/Audit/IngenicoAudit.php
A PaymentProviders/Ingenico/ReferenceData.php
A PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
A PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
A PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
A PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
6 files changed, 336 insertions(+), 0 deletions(-)

Approvals:
  Mepps: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/PaymentProviders/Ingenico/Audit/IngenicoAudit.php 
b/PaymentProviders/Ingenico/Audit/IngenicoAudit.php
new file mode 100644
index 0000000..738c06d
--- /dev/null
+++ b/PaymentProviders/Ingenico/Audit/IngenicoAudit.php
@@ -0,0 +1,197 @@
+<?php namespace SmashPig\PaymentProviders\Ingenico\Audit;
+
+use DOMDocument;
+use DOMElement;
+use RuntimeException;
+use SmashPig\Core\Logging\Logger;
+use SmashPig\Core\UtcDate;
+use SmashPig\PaymentProviders\Ingenico\ReferenceData;
+
+class IngenicoAudit {
+
+       protected $fileData = array();
+
+       protected $donationMap = array(
+               'PaymentAmount' => 'gross',
+               'IPAddressCustomer' => 'user_ip',
+               'BillingFirstname' => 'first_name',
+               'BillingSurname' => 'last_name',
+               'BillingStreet' => 'street_address',
+               'BillingCity' => 'city',
+               'ZipCode' => 'postal_code',
+               'BillingCountryCode' => 'country',
+               'BillingEmail' => 'email',
+               'AdditionalReference' => 'contribution_tracking_id',
+               'PaymentProductId' => 'gc_product_id',
+               'OrderID' => 'order_id',
+               'PaymentCurrency' => 'currency',
+               'AmountLocal' => 'gross',
+               'TransactionDateTime' => 'date',
+       );
+
+       protected $refundMap = array(
+               'DebitedAmount' => 'gross',
+               'AdditionalReference' => 'contribution_tracking_id',
+               'OrderID' => 'gateway_parent_id',
+               'DebitedCurrency' => 'gross_currency',
+               'TransactionDateTime' => 'date',
+       );
+
+       protected $recordsWeCanDealWith = array(
+               // Credit card item that has been processed, but not settled.
+               // We take these seriously.
+               // TODO: Why aren't we waiting for +ON?
+               'XON' => 'donation',
+               // Settled "Invoice Payment". Could be invoice, bt, rtbt, check,
+               // prepaid card, ew, cash
+               '+IP' => 'donation',
+               '-CB' => 'chargeback', // Credit card chargeback
+               '-CR' => 'refund', // Credit card refund
+               '+AP' => 'donation', // Direct Debit collected
+       );
+
+       public function parseFile( $path ) {
+               $unzippedFullPath = $this->getUnzippedFile( $path );
+
+               // load the XML into a DOMDocument.
+               // Total Memory Hog Alert. Handle with care.
+               $domDoc = new DOMDocument( '1.0' );
+               Logger::info( "Loading XML from $unzippedFullPath" );
+               $domDoc->load( $unzippedFullPath );
+               unlink( $unzippedFullPath );
+               Logger::info( "Processing" );
+
+               foreach ( $domDoc->getElementsByTagName( 'DataRecord' ) as 
$recordNode ) {
+                       $this->parseRecord( $recordNode );
+               }
+
+               return $this->fileData;
+       }
+
+       protected function parseRecord( DOMElement $recordNode ) {
+               $category = $recordNode->getElementsByTagName( 'Recordcategory' 
)
+                       ->item( 0 )->nodeValue;
+               $type = $recordNode->getElementsByTagName( 'Recordtype' )
+                       ->item( 0 )->nodeValue;
+
+               $compoundType = $category . $type;
+               if ( !array_key_exists( $compoundType, 
$this->recordsWeCanDealWith ) ) {
+                       return;
+               }
+
+               if ( $category === '-' ) {
+                       $refundType = 
$this->recordsWeCanDealWith[$compoundType];
+                       $record = $this->parseRefund( $recordNode, $refundType 
);
+               } else {
+                       $record = $this->parseDonation( $recordNode );
+               }
+               $record = $this->normalizeValues( $record );
+               // TODO: label Connect API donations as 'ingenico'
+               $record['gateway'] = 'globalcollect';
+
+               $this->fileData[] = $record;
+       }
+
+       protected function parseDonation( DOMElement $recordNode ) {
+               $record = $this->xmlToArray( $recordNode, $this->donationMap );
+               $record['gateway_txn_id'] = $record['order_id'];
+               $record = $this->addPaymentMethod( $record );
+               return $record;
+       }
+
+       protected function parseRefund( DOMElement $recordNode, $type ) {
+               $record = $this->xmlToArray( $recordNode, $this->refundMap );
+               $record['type'] = $type;
+               return $record;
+       }
+
+       protected function xmlToArray( DOMElement $recordNode, $map ) {
+               $record = array();
+               foreach ( $map as $theirs => $ours ) {
+                       foreach ( $recordNode->getElementsByTagName( $theirs ) 
as $recordItem ) {
+                               $record[$ours] = $recordItem->nodeValue;  // 
there 'ya go: Normal already.
+                       }
+               }
+               return $record;
+       }
+
+       /**
+        * Adds our normalized payment_method and payment_submethod params based
+        * on the codes that GC uses
+        *
+        * @param array $record The record from the wx file, in array format
+        * @return array The $record param with our normal keys appended
+        */
+       function addPaymentMethod( $record ) {
+               $normalized = ReferenceData::decodePaymentMethod(
+                       $record['gc_product_id']
+               );
+               $record = array_merge( $record, $normalized );
+
+               unset ( $record['gc_product_id'] );
+               return $record;
+       }
+
+       /**
+        * @param string $path Path to original zipped file
+        * @return string Path to unzipped file in working directory
+        */
+       protected function getUnzippedFile( $path ) {
+               $zippedParts = explode( DIRECTORY_SEPARATOR, $path );
+               $zippedFilename = array_pop( $zippedParts );
+               // TODO keep unzipped files around?
+               $workingDirectory = tempnam( sys_get_temp_dir(), 
'ingenico_audit' );
+               if ( file_exists( $workingDirectory ) ) {
+                       unlink( $workingDirectory );
+               }
+               mkdir( $workingDirectory );
+               // whack the .gz on the end
+               $unzippedFilename = substr( $zippedFilename, 0, strlen( 
$zippedFilename ) - 3 );
+
+               $copiedZipPath = $workingDirectory . DIRECTORY_SEPARATOR . 
$zippedFilename;
+               copy( $path, $copiedZipPath );
+               if ( !file_exists( $copiedZipPath ) ) {
+                       throw new RuntimeException(
+                               "FILE PROBLEM: Trying to copy $path to 
$copiedZipPath " .
+                               'for decompression, and something went wrong'
+                       );
+               }
+
+               $unzippedFullPath = $workingDirectory . DIRECTORY_SEPARATOR . 
$unzippedFilename;
+               // decompress
+               Logger::info( "Gunzipping $copiedZipPath" );
+               // FIXME portability
+               $cmd = "gunzip -f $copiedZipPath";
+               exec( escapeshellcmd( $cmd ) );
+
+               // now check to make sure the file you expect actually exists
+               if ( !file_exists( $unzippedFullPath ) ) {
+                       throw new RuntimeException(
+                               'FILE PROBLEM: Something went wrong with 
decompressing WX file: ' .
+                               "$cmd : $unzippedFullPath doesn't exist."
+                       );
+               }
+               return $unzippedFullPath;
+       }
+
+       /**
+        * Normalize amounts, dates, and IDs to match everything else in 
SmashPig
+        * FIXME: do this with transformers migrated in from DonationInterface
+        *
+        * @param array $record
+        * @return array The record, with values normalized
+        */
+       protected function normalizeValues( $record ) {
+               if ( isset( $record['gross'] ) ) {
+                       $record['gross'] = $record['gross'] / 100;
+               }
+               if ( isset( $record['contribution_tracking_id'] ) ) {
+                       $parts = explode( '.', 
$record['contribution_tracking_id'] );
+                       $record['contribution_tracking_id'] = $parts[0];
+               }
+               if ( isset( $record['date'] ) ) {
+                       $record['date'] = UtcDate::getUtcTimestamp( 
$record['date'] );
+               }
+               return $record;
+       }
+}
diff --git a/PaymentProviders/Ingenico/ReferenceData.php 
b/PaymentProviders/Ingenico/ReferenceData.php
new file mode 100644
index 0000000..25e5a86
--- /dev/null
+++ b/PaymentProviders/Ingenico/ReferenceData.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace SmashPig\PaymentProviders\Ingenico;
+
+use OutOfBoundsException;
+
+class ReferenceData {
+       // FIXME: replace this whole class with payment_(sub)method.yaml files
+
+       static $methods = array(
+               '1' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'visa' ),
+               '2' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'amex' ),
+               '3' => array( 'payment_method' => 'cc', 'payment_submethod' => 
'mc' ),
+               '11' => array( 'payment_method' => 'bt', 'payment_submethod' => 
'bt' ),
+               '117' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'maestro' ),
+               '118' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'solo' ),
+               '124' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'laser' ),
+               '125' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'jcb' ),
+               '128' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'discover' ),
+               '130' => array( 'payment_method' => 'cc', 'payment_submethod' 
=> 'cb' ),
+               '500' => array( 'payment_method' => 'obt', 'payment_submethod' 
=> 'bpay' ),
+               '701' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_nl' ),
+               '702' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_de' ),
+               '703' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_at' ),
+               '704' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_fr' ),
+               '705' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_gb' ),
+               '706' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_be' ),
+               '707' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_ch' ),
+               '708' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_it' ),
+               '709' => array( 'payment_method' => 'dd', 'payment_submethod' 
=> 'dd_es' ),
+               '805' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_nordea_sweden' ),
+               '809' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_ideal' ),
+               '810' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_enets' ),
+               '836' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_sofortuberweisung' ),
+               '840' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_paypal' ),
+               '841' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_webmoney' ),
+               '843' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_moneybookers' ),
+               '845' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_cashu' ),
+               '849' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_yandex' ),
+               '856' => array( 'payment_method' => 'rtbt', 'payment_submethod' 
=> 'rtbt_eps' ),
+               '861' => array( 'payment_method' => 'ew', 'payment_submethod' 
=> 'ew_alipay' ),
+               '1503' => array( 'payment_method' => 'cash', 
'payment_submethod' => 'cash_boleto' ),
+       );
+
+       /**
+        * Gets our normalized payment_method and payment_submethod params from 
the
+        * codes that GC uses
+        *
+        * @param string $paymentProductId
+        * @return array containing payment_method and payment_submethod
+        */
+       public static function decodePaymentMethod( $paymentProductId ) {
+               if ( !array_key_exists( $paymentProductId, self::$methods ) ) {
+                       throw new OutOfBoundsException( "Unknown Payment 
Product ID $paymentProductId " );
+               }
+               return self::$methods[$paymentProductId];
+       }
+}
diff --git a/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz 
b/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
new file mode 100644
index 0000000..b38a947
--- /dev/null
+++ b/PaymentProviders/Ingenico/Tests/Data/chargeback.xml.gz
Binary files differ
diff --git a/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz 
b/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
new file mode 100644
index 0000000..ebb1109
--- /dev/null
+++ b/PaymentProviders/Ingenico/Tests/Data/donation.xml.gz
Binary files differ
diff --git a/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz 
b/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
new file mode 100644
index 0000000..8fc709c
--- /dev/null
+++ b/PaymentProviders/Ingenico/Tests/Data/refund.xml.gz
Binary files differ
diff --git a/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php 
b/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
new file mode 100644
index 0000000..3fe756c
--- /dev/null
+++ b/PaymentProviders/Ingenico/Tests/phpunit/AuditTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace SmashPig\PaymentProviders\Ingenico\Tests;
+
+use SmashPig\PaymentProviders\Ingenico\Audit\IngenicoAudit;
+use SmashPig\Tests\BaseSmashPigUnitTestCase;
+
+/**
+ * @group Audit
+ * @group Ingenico
+ */
+class AuditTest extends BaseSmashPigUnitTestCase {
+       /**
+        * Normal donation
+        */
+       public function testProcessDonation() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/donation.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one donation' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'gross' => 3.00,
+                       'contribution_tracking_id' => '5551212',
+                       'currency' => 'USD',
+                       'order_id' => '987654321',
+                       'gateway_txn_id' => '987654321',
+                       'payment_method' => 'cc',
+                       'payment_submethod' => 'visa',
+                       'date' => 1501368968,
+                       'user_ip' => '111.222.33.44',
+                       'first_name' => 'Arthur',
+                       'last_name' => 'Aardvark',
+                       'street_address' => '1111 Fake St',
+                       'city' => 'Denver',
+                       'country' => 'US',
+                       'email' => '[email protected]',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse 
donation correctly' );
+       }
+
+       /**
+        * Now try a refund
+        */
+       public function testProcessRefund() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/refund.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one refund' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'contribution_tracking_id' => '5551212',
+                       'date' => 1500942220,
+                       'gross' => 100,
+                       'gateway_parent_id' => '123456789',
+                       'gross_currency' => 'USD',
+                       'type' => 'refund',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse refund 
correctly' );
+       }
+
+       /**
+        * And a chargeback
+        */
+       public function testProcessChargeback() {
+               $processor = new IngenicoAudit();
+               $output = $processor->parseFile( __DIR__ . 
'/../Data/chargeback.xml.gz' );
+               $this->assertEquals( 1, count( $output ), 'Should have found 
one chargeback' );
+               $actual = $output[0];
+               $expected = array(
+                       'gateway' => 'globalcollect', // TODO: switch to 
ingenico for Connect
+                       'contribution_tracking_id' => '5551212',
+                       'date' => 1495023569,
+                       'gross' => 200,
+                       'gateway_parent_id' => '5167046621',
+                       'gross_currency' => 'USD',
+                       'type' => 'chargeback',
+               );
+               $this->assertEquals( $expected, $actual, 'Did not parse 
chargeback correctly' );
+       }
+}

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie85c11fca0f4155359361119420fca6325f5bb9b
Gerrit-PatchSet: 7
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Ejegg <[email protected]>
Gerrit-Reviewer: Mepps <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to