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