Katie Horn has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/159631

Change subject: Bug fixes for the initial commit of the WP recon parser
......................................................................

Bug fixes for the initial commit of the WP recon parser

Change-Id: Ib8123297a114a20981024447c90201991d62f698
---
M PaymentProviders/WorldPay/Audit/ReferenceData.php
M PaymentProviders/WorldPay/Audit/TransactionReconciliationFile.php
M PaymentProviders/WorldPay/Audit/WorldPayAudit.php
3 files changed, 199 insertions(+), 160 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/SmashPig 
refs/changes/31/159631/1

diff --git a/PaymentProviders/WorldPay/Audit/ReferenceData.php 
b/PaymentProviders/WorldPay/Audit/ReferenceData.php
index 0b25a6a..ac83870 100644
--- a/PaymentProviders/WorldPay/Audit/ReferenceData.php
+++ b/PaymentProviders/WorldPay/Audit/ReferenceData.php
@@ -104,6 +104,6 @@
                        }
                }
 
-               throw new Exception( "Unknown card type code: {$code}" );
+               throw new \Exception( "Unknown card type code: {$code}" );
        }
 }
diff --git a/PaymentProviders/WorldPay/Audit/TransactionReconciliationFile.php 
b/PaymentProviders/WorldPay/Audit/TransactionReconciliationFile.php
index 3edeced..5e373af 100644
--- a/PaymentProviders/WorldPay/Audit/TransactionReconciliationFile.php
+++ b/PaymentProviders/WorldPay/Audit/TransactionReconciliationFile.php
@@ -1,12 +1,18 @@
 <?php
+
 namespace SmashPig\PaymentProviders\WorldPay\Audit;
+
+require_once "ReferenceData.php";
 
 /**
  * See EMIS User Guide V9 January 2014.pdf
  */
 class TransactionReconciliationFile {
-    protected $filenamePattern = '/^MA\.PISCESSW\.#M\.RECON\..*/';
+
+       const filenamePattern = '/^MA\.PISCESSW\.#M\.RECON\..*/';
+
        protected $recordTypes;
+       protected $fileData = array( );
 
        function __construct() {
                $this->row_header_segment = array(
@@ -29,7 +35,7 @@
                        array( "pending_credits_count", 7 ),
                );
 
-               $file_header = new FixedRecordType( "00", array(
+               $file_header = new FixedLengthRecordSpecification( "00", array(
                        array(
                                array( "record_type", 2 ),
                                array( "sequence_no", 8 ),
@@ -42,9 +48,9 @@
                                array( "file_sequence_number", 3 ),
                                array( "site_id", 3 ),
                        )
-               ) );
+                       ) );
 
-               $reconciliation_merchant_company_header = new FixedRecordType( 
"05", array(
+               $reconciliation_merchant_company_header = new 
FixedLengthRecordSpecification( "05", array(
                        array(
                                array( "record_type", 2 ),
                                array( "sequence_no", 8 ),
@@ -52,9 +58,9 @@
                                array( "count", 7 ),
                        ),
                        $credit_debit_summary_segment
-               ) );
+                       ) );
 
-               $reconciliation_merchant_outlet = new FixedRecordType( "10", 
array(
+               $reconciliation_merchant_outlet = new 
FixedLengthRecordSpecification( "10", array(
                        array(
                                array( "record_type", 2 ),
                                array( "sequence_no", 8 ),
@@ -63,42 +69,42 @@
                                array( "processing_date", 6 ),
                        ),
                        $credit_debit_summary_segment
-               ) );
+                       ) );
 
-               $this->reconciliation_transaction_data = new 
FixedRecordType("15", array( array(
-                       array( "record_type", 2 ),
-                       array( "sequence_no", 8 ),
-                       array( "pan", 19 ),
-                       array( "expiry_date", 4 ),
-                       array( "transaction_value", 11 ),
-                       array( "transaction_date", 6 ),
-                       array( "transaction_time", 6 ),
-                       array( "transaction_type", 1 ),
-                       array( "transaction_source", 1 ),
-                       array( "receipt_number", 6 ),
-                       array( "status", 1 ),
-                       array( "reserved1", 2 ),
-                       array( "local_value", 9 ),
-                       array( "local_currency_code", 3 ),
-                       array( "local_exponent", 1 ),
-                       array( "settlement_value", 9 ),
-                       array( "settlement_currency_code", 3 ),
-                       array( "settlement_exponent", 1 ),
-                       array( "acquired_processed_indicator", 1 ),
-                       array( "card_type", 5 ),
-               ) ) );
+               $this->reconciliation_transaction_data = new 
FixedLengthRecordSpecification( "15", array( array(
+                               array( "record_type", 2 ),
+                               array( "sequence_no", 8 ),
+                               array( "pan", 19 ),
+                               array( "expiry_date", 4 ),
+                               array( "transaction_value", 11 ),
+                               array( "transaction_date", 6 ),
+                               array( "transaction_time", 6 ),
+                               array( "transaction_type", 1 ),
+                               array( "transaction_source", 1 ),
+                               array( "receipt_number", 6 ),
+                               array( "status", 1 ),
+                               array( "reserved1", 2 ),
+                               array( "local_value", 9 ),
+                               array( "local_currency_code", 3 ),
+                               array( "local_exponent", 1 ),
+                               array( "settlement_value", 9 ),
+                               array( "settlement_currency_code", 3 ),
+                               array( "settlement_exponent", 1 ),
+                               array( "acquired_processed_indicator", 1 ),
+                               array( "card_type", 5 ),
+                       ) ) );
 
-               $this->reconciliation_transaction_supplementary_data = new 
FixedRecordType( "16", array( array(
-                       array( "record_type", 2 ),
-                       array( "sequence_no", 8 ),
-                       array( "auth_code", 6 ),
-                       array( "auth_method", 1 ),
-                       array( "card_issue_number", 2 ),
-                       array( "card_start_date", 4 ),
-                       array( "cash_amount", 7 ),
-                       array( "originators_transaction_reference", 20 ),
-                       array( "ticket_number", 14 ),
-               ) ) );
+               $this->reconciliation_transaction_supplementary_data = new 
FixedLengthRecordSpecification( "16", array( array(
+                               array( "record_type", 2 ),
+                               array( "sequence_no", 8 ),
+                               array( "auth_code", 6 ),
+                               array( "auth_method", 1 ),
+                               array( "card_issue_number", 2 ),
+                               array( "card_start_date", 4 ),
+                               array( "cash_amount", 7 ),
+                               array( "originators_transaction_reference", 20 
),
+                               array( "ticket_number", 14 ),
+                       ) ) );
 
                $this->recordTypes = array(
                        $file_header,
@@ -111,186 +117,198 @@
 
        static function isMine( $path ) {
                $filename = basename( $path );
-        return preg_match( $this->filenamePattern, $filename );
+               return preg_match( self::filenamePattern, $filename );
        }
 
        function parse( $path ) {
-        $this->pending_data = null;
-        $this->pending_supplemental_data = null;
+               $this->pending_data = null;
+               $this->pending_supplemental_data = null;
 
                $this->path = $path;
-        $this->file = fopen( $path, 'r' );
-               
+               $this->file = fopen( $path, 'r' );
+
                while ( $line = fgets( $this->file ) ) {
-            $this->parse_line( $line );
+                       $this->parse_line( $line );
                }
                fclose( $this->file );
 
-        $this->flush_data();
+               $this->flush_data();
+               return $this->fileData;
        }
 
        /**
-        * Parse one line and send it to the appropriate queue
+        * Parse one line.
         *
         * There is a crazy thing happening here where we need to coordinate
         * sequential lines, and merge supplemental records into the main 
transaction
         * data.  See add_transaction_data and add_supplementary_data.
         */
-    protected function parse_line( $line ) {
-        # Peek at row header to determine its type
-        $row_info = FixedLengthRecordParser::parse( $this->row_header_segment, 
substr( line, 0, 10 ) );
+       protected function parse_line( $line ) {
+               # Peek at row header to determine its type
+               $row_info = FixedLengthRecordParser::parse( 
$this->row_header_segment, substr( $line, 0, 10 ) );
 
-        # Find the corresponding line format and parse the contents
-        $record = null;
-        foreach ( $this->recordTypes as $type ) {
-            if ( $record_type === $row_info['record_type'] ) {
-                $record = FixedLengthRecordParser::parse( 
$record_type->fields, $line );
+               # Find the corresponding line format and parse the contents
+               $record = null;
+               foreach ( $this->recordTypes as $type ) {
+                       if ( $type->getPrefix() === $row_info['record_type'] ) {
+                               $record = FixedLengthRecordParser::parse( 
$type->fields, $line );
                        }
                }
 
-        if ( !$record ) {
-                       // FIXME: correct exception type, and get context 
(path) from log context
-            throw new Exception( "Unknown record type 
{$row_info['record_type']} while processing {$this->path}, aborting!" );
+               if ( !$record ) {
+                       // FIXME: correct Exception type, and get context 
(path) from log context
+                       // SUPERFIXME: This introduces an extreme degree of 
fragility into the system. One weird line can't blow up the whole thing; We'd 
get killed by the schrapnel on the daily.
+//                     throw new \Exception( "Unknown record type 
{$row_info['record_type']} while processing {$this->path}, aborting!" );
+                       //for now...
+                       error_log( "Unknown record type 
{$row_info['record_type']} while processing {$this->path}. Rolling over it." );
                }
 
-        # Dispatch to a handler
-        if ( $record["record_type"] === 
$this->reconciliation_transaction_data->record_type ) {
-            $this->add_transaction_data(record);
-        } elseif ( $record["record_type"] === 
$this->reconciliation_transaction_supplementary_data->record_type ) {
-            $this->add_supplementary_data(record);
+               # Dispatch to a handler
+               $record_type = false;
+               if ( array_key_exists( 'record_type', $record ) ) {
+                       $record_type = $record['record_type'];
+               }
+               if ( $record_type === 
$this->reconciliation_transaction_data->getPrefix() ) {
+                       $this->add_transaction_data( $record );
+               } elseif ( $record_type === 
$this->reconciliation_transaction_supplementary_data->getPrefix() ) {
+                       $this->add_supplementary_data( $record );
                } else {
-            # ignore other crap.
-            # TODO: assertions for checksummy things built into the file
+                       # ignore other crap.
+                       # TODO: assertions for checksummy things built into the 
file
                }
        }
 
-    protected function add_transaction_data( $record ) {
-        $this->flush_data();
-        $this->pending_data = $record;
+       protected function add_transaction_data( $record ) {
+               $this->flush_data();
+               $this->pending_data = $record;
        }
 
-    protected function add_supplementary_data( $record ) {
-        if ( !$this->pending_data ) {
+       protected function add_supplementary_data( $record ) {
+               if ( !$this->pending_data ) {
                        // TODO: get line number from the context
-            throw new Exception( "Cannot eat supplementary transaction data 
because there no unconsumed base data. Line {$record["sequence_no"]}" );
+                       throw new \Exception( "Cannot eat supplementary 
transaction data because there no unconsumed base data. Line 
{$record["sequence_no"]}" );
                }
 
-        if ( $this->pending_supplemental_data ) {
-            throw new Exception( "Cannot eat supplementary data because there 
is already unconsumed supplemental data. Line {$record["sequence_no"]}" );
+               if ( $this->pending_supplemental_data ) {
+                       throw new \Exception( "Cannot eat supplementary data 
because there is already unconsumed supplemental data. Line 
{$record["sequence_no"]}" );
                }
 
-        $this->pending_supplemental_data = $record;
+               $this->pending_supplemental_data = $record;
+               $this->flush_data();
        }
 
-    protected function flush_data() {
-        if ( $this->pending_data ) {
-            $this->send_transaction();
+       protected function flush_data() {
+               if ( $this->pending_data ) {
+                       $this->send_transaction();
                }
        }
 
-    protected function send_transaction() {
-        $record = $this->pending_data;
+       protected function send_transaction() {
+               $record = $this->pending_data;
 
-        # Verify that the data and supplemental data are a pair
-        if ( $this->pending_supplemental_data ) {
-            if ( $this->pending_supplemental_data['sequence_no'] !== ( 
$this->pending_data['sequence_no'] + 1 ) ) {
-                throw new Exception( "Mismatched data and supplemental data!" 
);
+               # Verify that the data and supplemental data are a pair
+               if ( $this->pending_supplemental_data ) {
+                       if ( ( int ) 
($this->pending_supplemental_data['sequence_no']) !== ( 
$this->pending_data['sequence_no'] + 1 ) ) {
+                               throw new \Exception( "Mismatched data and 
supplemental data! " . print_r( $this->pending_supplemental_data, true ) . " " 
. print_r( $this->pending_data, true ) );
                        }
-            $record = array_merge( $record, $this->pending_supplemental_data );
+                       $record = array_merge( $record, 
$this->pending_supplemental_data );
                }
 
-        $this->normalize_and_send( $record );
+               $msg = $this->normalize( $record );
 
-        $this->pending_data = null;
-        $this->pending_supplemental_data = null;
+               $this->pending_data = null;
+               $this->pending_supplemental_data = null;
+
+               $this->fileData[] = $msg;
        }
 
        /**
-        * Transform the record into a WMF queue message
+        * Normalize the pieces of the message that exist, according to the
+        * definition of a standard WMF queue message.
+        *
+        * Defaults should always be left up to the relevant queue consumer.
         *
         * See https://wikitech.wikimedia.org/wiki/Fundraising/Queue_messages
         */
-    protected function normalize_and_send( $record ) {
-        $msg = array();
+       protected function normalize( $record ) {
+               $msg = array( );
 
-        if ( $record["transaction_type"] == "0" ) {
-            $queue = "donations";
-        } elseif ( $record["transaction_type"] == "5" ) {
-            $queue = "refund";
-        } else {
-            throw new Exception( "Don't know how to handle transaction type 
{$record["transaction_type"]}" );
-               }
-
-        $msg["date"] = strtotime( $record["transaction_date"] . 'T' . 
$record["transaction_time"] . "Z00" );
-
-        $msg["gateway"] = "worldpay";
-
-        if ( array_key_exists( "originators_transaction_reference", $record ) 
) {
-            $msg["gateway_txn_id"] = trim( 
$record["originators_transaction_reference"] );
-        } else {
-            throw new Exception( "We're gonna die: no gateway_txn_id 
available." );
-               }
-
-        # The default currency is GBP, don't make me explain why the amount
-        # comes from a different field when currency != GBP :(
-        if ( trim( $record["local_currency_code"] ) ) {
-            $msg["currency"] = $record["local_currency_code"];
-            $msg["gross"] = int($record["local_value"]) * 
self::exponent_to_multiplier($record["local_exponent"]);
+               if ( $record["transaction_type"] == "0" ) {
+                       $queue = "donations";
+               } elseif ( $record["transaction_type"] == "5" ) {
+                       $queue = "refund";
                } else {
-            $msg["currency"] = "GBP";
-            $msg["gross"] = int($record["transaction_value"]) * 
self::exponent_to_multiplier(2);
+                       //FIXME: This also seems incredibly fragile.
+                       throw new \Exception( "Don't know how to handle 
transaction type {$record["transaction_type"]}" );
                }
 
-        if ( $queue === "refund" ) {
-            $msg["gross_currency"] = $msg["currency"];
-            $msg["gateway_parent_id"] = $msg["gateway_txn_id"];
-            # Note that we do not have a new txn id for the refund
-            $msg["gateway_refund_id"] = $msg["gateway_txn_id"];
-            # FIXME: chargeback vs refund info is not available in this file.
-            $msg["type"] = "refund";
-            
#log.info("+Sending\t{id}\t{date}\t{type}".format(id=msg["gateway_parent_id"], 
date=iso_date, type=msg["type"]))
-            #self.send(queue, msg)
-            return;
+               $msg["date"] = strtotime( $record["transaction_date"] . 'T' . 
$record["transaction_time"] . "Z00" );
+
+               $msg["gateway"] = "worldpay";
+
+               if ( array_key_exists( "originators_transaction_reference", 
$record ) ) {
+                       $msg["gateway_txn_id"] = trim( 
$record["originators_transaction_reference"] );
+               } else {
+                       throw new \Exception( "We're gonna die: no 
gateway_txn_id available." );
+               }
+
+               # The default currency is GBP, don't make me explain why the 
amount
+               # comes from a different field when currency != GBP :(
+               if ( trim( $record["local_currency_code"] ) ) {
+                       $msg["currency"] = $record["local_currency_code"];
+                       $msg["gross"] = ( int ) ( $record["local_value"] ) * 
self::exponent_to_multiplier( $record["local_exponent"] );
+               } else {
+                       $msg["currency"] = "GBP";
+                       $msg["gross"] = ( int ) ( $record["transaction_value"] 
) * self::exponent_to_multiplier( 2 );
+               }
+
+               if ( $queue === "refund" ) {
+                       $msg["gross_currency"] = $msg["currency"];
+                       $msg["gateway_parent_id"] = $msg["gateway_txn_id"];
+                       # Note that we do not have a new txn id for the refund
+                       $msg["gateway_refund_id"] = $msg["gateway_txn_id"];
+                       # FIXME: chargeback vs refund info is not available in 
this file.
+                       $msg["type"] = "refund";
+                       
#log.info("+Sending\t{id}\t{date}\t{type}".format(id=msg["gateway_parent_id"], 
date=iso_date, type=msg["type"]))
+                       #self.send(queue, msg)
+                       return;
                }
 
                /*
-        if self.crm.transaction_exists(gateway_txn_id=msg["gateway_txn_id"], 
gateway="worldpay"):
-            
log.info("-Duplicate\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
-            return
-               */
+                 if 
self.crm.transaction_exists(gateway_txn_id=msg["gateway_txn_id"], 
gateway="worldpay"):
+                 
log.info("-Duplicate\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
+                 return
+                */
 
-        # Switch behavior depending on the status.  We only like "accepted" 
transactions.
-        $status = trim( $record["status"] );
-        if ( $status == "P" ) {
-            
#log.info("-Pending\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
-            return;
+               # Switch behavior depending on the status.  We only like 
"accepted" transactions.
+               $status = trim( $record["status"] );
+               if ( $status == "P" ) {
+                       
#log.info("-Pending\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
+                       return;
                } elseif ( $status == "R" ) {
-            
#log.info("-Rejection\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
-            return;
+                       
#log.info("-Rejection\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
+                       return;
                } elseif ( $status != "A" ) {
-            throw new Exception( "Unknown gateway status: {$status}" );
+                       throw new \Exception( "Unknown gateway status: 
{$status}" );
                }
 
-        # Include settlement details if they are available.
-        if ( trim( $record["settlement_value"] ) ) {
-            if ( trim( $record["settlement_currency_code"] ) ) {
-                $msg["settlement_currency"] = 
$record["settlement_currency_code"];
-            } else {
-                $msg["settlement_currency"] = "GBP";
+               # Include settlement details if they are available.
+               if ( trim( $record["settlement_value"] ) ) {
+                       if ( trim( $record["settlement_currency_code"] ) ) {
+                               $msg["settlement_currency"] = 
$record["settlement_currency_code"];
+                       } else {
+                               $msg["settlement_currency"] = "GBP";
                        }
-            $msg["settlement_amount"] = int($record["settlement_value"]) * 
self::exponent_to_multiplier($record["settlement_exponent"]);
+                       $msg["settlement_amount"] = ( int ) ( 
$record["settlement_value"] ) * self::exponent_to_multiplier( 
$record["settlement_exponent"] );
                }
 
-               // TODO: call another component to pull missing details from 
logs
-        $msg["email"] = "[email protected]";
-        $msg["payment_method"] = "cc";
-        $msg["payment_submethod"] = ReferenceData::decode_card_type( trim( 
$record["card_type"] ) );
+               $msg["payment_method"] = "cc"; //this one is okay, because WP 
only does cc at this point. Maybe forever?
+               $msg["payment_submethod"] = ReferenceData::decode_card_type( 
trim( $record["card_type"] ) );
 
-        # custom values
-        $msg["raw_card_type"] = trim( $record["card_type"] );
+               # custom values
+               $msg["raw_card_type"] = trim( $record["card_type"] );
 
-        
#log.info("+Sending\t{id}\t{date}\t{type}".format(id=msg["gateway_txn_id"], 
date=iso_date, type=queue))
-        #$self.send(queue, msg)
+               return $msg;
        }
 
        /**
@@ -310,13 +328,15 @@
                        $exponent = 2;
                }
 
-               return pow( 10, 0 - int( $exponent ) );
+               return pow( 10, 0 - ( int ) ( $exponent ) );
        }
+
 }
 
 class FixedLengthRecordSpecification {
+
        public $prefix;
-       public $fields = array();
+       public $fields = array( );
 
        function __construct( $prefix, $fieldSegments ) {
                $this->prefix = $prefix;
@@ -325,19 +345,28 @@
                        $this->fields = array_merge( $this->fields, $segment );
                }
        }
+
+       public function getPrefix() {
+               return $this->prefix;
+       }
+
 }
 
 /**
  * FIXME: not compatible with Wr1 field specs, yet
  */
 class FixedLengthRecordParser {
+
        static function parse( $fields, $line ) {
                $pos = 0;
-               $out = array();
+               $out = array( );
                foreach ( $fields as $field ) {
                        list( $fieldName, $length ) = $field;
-                       $out[$fieldName] = substr( $line, $pos, $pos + $length 
);
+                       $out[$fieldName] = substr( $line, $pos, $length );
+                       $pos += $length;
                }
+               $pos = 0;
                return $out;
        }
+
 }
diff --git a/PaymentProviders/WorldPay/Audit/WorldPayAudit.php 
b/PaymentProviders/WorldPay/Audit/WorldPayAudit.php
index 75618d3..d0efaca 100644
--- a/PaymentProviders/WorldPay/Audit/WorldPayAudit.php
+++ b/PaymentProviders/WorldPay/Audit/WorldPayAudit.php
@@ -1,17 +1,27 @@
 <?php
+
 namespace SmashPig\PaymentProviders\WorldPay\Audit;
 
 class WorldPayAudit {
+
        function retrieveFiles() {
                AuditRetriever::retrieveAll( 'worldpay' );
        }
 
-       function parseFiles( $files ) {
+       function parseFile( $file ) {
                // FIXME: this should be specified in configuration
                $fileTypes = array(
                        
'SmashPig\PaymentProviders\WorldPay\Audit\TransactionReconciliationFile',
                );
 
-               AuditParser::parseAll( $files, $fileTypes );
+               foreach ( $fileTypes as $type ) {
+                       if ( $type::isMine( $file ) ) {
+                               $parser = new $type();
+                               $data = $parser->parse( $file );
+                       }
+               }
+
+               return $data;
        }
+
 }

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib8123297a114a20981024447c90201991d62f698
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Katie Horn <[email protected]>

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

Reply via email to