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