Ejegg has submitted this change and it was merged.

Change subject: Minimum split of BaseAuditProcessor from functions that don't 
require context
......................................................................


Minimum split of BaseAuditProcessor from functions that don't require context

Change-Id: I60da6b9ca7f542a32cf01ff8446ac7e68f866e6e
---
A sites/all/modules/wmf_audit/BaseAuditProcessor.php
M sites/all/modules/wmf_audit/wmf_audit.info
M sites/all/modules/wmf_audit/wmf_audit.module
A sites/all/modules/wmf_audit/worldpay/WorldpayAuditProcessor.php
M sites/all/modules/wmf_audit/worldpay/worldpay_audit.info
M sites/all/modules/wmf_audit/worldpay/worldpay_audit.module
6 files changed, 1,360 insertions(+), 1,314 deletions(-)

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



diff --git a/sites/all/modules/wmf_audit/BaseAuditProcessor.php 
b/sites/all/modules/wmf_audit/BaseAuditProcessor.php
new file mode 100644
index 0000000..95cc826
--- /dev/null
+++ b/sites/all/modules/wmf_audit/BaseAuditProcessor.php
@@ -0,0 +1,860 @@
+<?php
+
+abstract class BaseAuditProcessor {
+    protected $options;
+
+    public function __construct( $options ) {
+       $this->options = $options;
+       // FIXME: Copy to confusing global thing.
+       wmf_audit_runtime_options( $options );
+    }
+
+    // FIXME: This is a huge interface.  Simplify and find more shared code.
+    abstract function get_compressed_log_file_name( $date );
+    abstract function get_log_days_in_past();
+    abstract function get_log_distilling_grep_string();
+    abstract function get_log_line_grep_string( $order_id );
+    abstract function get_log_line_xml_data_nodes();
+    abstract function get_log_line_xml_outermost_node();
+    abstract function get_log_line_xml_parent_nodes();
+    abstract function get_order_id( $transaction );
+    abstract function get_recon_completed_dir();
+    abstract function get_recon_dir();
+    abstract function get_recon_file_date( $file );
+    abstract function get_record_human_date( $record );
+    abstract function get_uncompressed_log_file_name( $date );
+    abstract function get_working_log_dir();
+    abstract function get_working_log_file_date( $file );
+    abstract function get_working_log_file_name( $date );
+    abstract function main_transaction_exists_in_civi( $transaction );
+    abstract function negative_transaction_exists_in_civi( $transaction );
+    abstract function normalize_and_merge_data( $data, $transaction );
+    abstract function normalize_negative( $record );
+    abstract function normalize_partial( $all_data );
+    abstract function parse_recon_file( $file );
+    abstract function record_is_chargeback( $record );
+    abstract function record_is_refund( $record );
+    abstract function regex_for_compressed_log();
+    abstract function regex_for_recon();
+    abstract function regex_for_uncompressed_log();
+    abstract function regex_for_working_log();
+
+    protected function get_runtime_options( $name ) {
+       if ( isset( $this->options[$name] ) ) {
+           return $this->options[$name];
+       } else {
+           return null;
+       }
+    }
+
+    /**
+     * The main function, intended to be called straight from the drush 
command and
+     * nowhere else.
+     */
+    public function run() {
+       civicrm_initialize();
+
+       //make sure all the things we need are there.
+       if (!$this->setup_required_directories()) {
+         throw new Exception( 'Missing required directories' );
+       }
+
+       //find out what the situation is with the available recon files, by date
+       $recon_files = $this->get_all_recon_files();
+
+       //get missing transactions from one or more recon files
+       //let's just assume that the default mode will be to pop off the top 
three (most recent) at this point. :)
+       //...Three, because Shut Up.
+       $count = count($recon_files);
+       if ($count > 3 && !$this->get_runtime_options('run_all')) {
+         $count = 3;
+       }
+
+       $total_missing = array();
+       $recon_file_stats = array();
+
+       for ($i = 0; $i < $count; ++$i) {
+         $parsed = array();
+         $missing = array();
+
+         //parce the recon files into something relatively reasonable.
+         $file = array_pop($recon_files);
+         wmf_audit_echo("Parsing $file");
+         $start_time = microtime(true);
+         $parsed = $this->parse_recon_file( $file );
+         $time = microtime(true) - $start_time;
+         wmf_audit_echo(count($parsed) . " results found in $time seconds\n");
+
+         //remove transactions we already know about
+         $start_time = microtime(true);
+         $missing = $this->get_missing_transactions($parsed);
+         $recon_file_stats[$file] = wmf_audit_count_missing($missing);
+         $time = microtime(true) - $start_time;
+         wmf_audit_echo(wmf_audit_count_missing($missing) . ' missing 
transactions (of a possible ' . count($parsed) . ") identified in $time 
seconds\n");
+
+         //If the file is empty, move it off.
+         // Note that we are not archiving files that have missing 
transactions,
+         // which might be resolved below.  Those are archived on the next run,
+         // once we can confirm they have hit Civi and are no longer missing.
+         if (wmf_audit_count_missing($missing) <= 
$this->get_runtime_options('recon_complete_count')) {
+           wmf_audit_move_completed_recon_file($file, 
$this->get_recon_completed_dir());
+         }
+
+         //grumble...
+         if (!empty($missing)) {
+           foreach ($missing as $type => $data) {
+             if (!empty($missing[$type])) {
+               if (array_key_exists($type, $total_missing)) {
+                 $total_missing[$type] = array_merge($total_missing[$type], 
$missing[$type]);
+               } else {
+                 $total_missing[$type] = $missing[$type];
+               }
+             }
+           }
+         }
+       }
+       $total_missing_count = wmf_audit_count_missing($total_missing);
+       wmf_audit_echo("$total_missing_count total missing transactions 
identified at start");
+
+       //get the date distribution on what's left... for ***main transactions 
only***
+       //That should be to say: The things that are totally in the payments 
logs.
+       //Other things, we will have to look other places, or just rebuild.
+       $missing_by_date = array();
+       if (array_key_exists('main', $total_missing) && 
!empty($total_missing['main'])) {
+         foreach ($total_missing['main'] as $record) {
+           $missing_by_date[$this->get_record_human_date( $record )][] = 
$record;
+         }
+       }
+
+
+       $remaining = null;
+       if (!empty($missing_by_date)) {
+         $remaining['main'] = $this->log_hunt_and_send($missing_by_date);
+       }
+
+       //@TODO: Handle the recurring type, once we have a gateway that gives 
some of those to us.
+       //
+       //Handle the negatives now. That way, the parent transactions will 
probably exist.
+       wmf_audit_echo("Processing 'negative' transactions");
+       $neg_count = 0;
+
+       if (array_key_exists('negative', $total_missing) && 
!empty($total_missing['negative'])) {
+         foreach ($total_missing['negative'] as $record) {
+           //check to see if the parent exists. If it does, normalize and send.
+           if (worldpay_audit_main_transaction_exists_in_civi($record)) {
+             $normal = $this->normalize_negative( $record );
+             if (wmf_audit_send_transaction($normal, 'negative')) {
+               $neg_count += 1;
+               wmf_audit_echo('!');
+             }
+             wmf_audit_echo('X');
+           } else {
+             //@TODO: Some version of makemissing should make these, too. Gar.
+             $remaining['negative'][$this->get_record_human_date( $record )][] 
= $record;
+             wmf_audit_echo('.');
+           }
+         }
+         wmf_audit_echo("Processed $neg_count 'negative' transactions\n");
+       }
+
+       //Wrap it up and put a bow on it.
+       //@TODO much later: Make a fredge table for these things and dump some 
messages over there about what we just did.
+       $missing_main = 0;
+       $missing_negative = 0;
+       $missing_recurring = 0;
+       $missing_at_end = 0;
+       if (is_array($remaining) && !empty($remaining)) {
+         foreach ($remaining as $type => $data) {
+           $count = wmf_audit_count_missing($data);
+           ${'missing_' . $type} = $count;
+           $missing_at_end += $count;
+         }
+       }
+
+       $wrap_up = "\nDone! Final stats:\n";
+       $wrap_up .= "Total missing at start: $total_missing_count\n";
+       $wrap_up .= 'Missing at end: ' . $missing_at_end . "\n\n";
+
+       if ($missing_at_end > 0) {
+         $wrap_up .= "Missing transaction summary:\n";
+         $wrap_up .= "Regular donations: $missing_main\n";
+         if ($missing_main > 0) {
+           foreach ($remaining['main'] as $date => $missing) {
+             if (count($missing) > 0) {
+               $wrap_up .= "\t$date: " . count($missing) . "\n";
+             }
+           }
+         }
+         $wrap_up .= "Refunds and chargebacks: $missing_negative\n";
+         if ($missing_negative > 0) {
+           foreach ($remaining['negative'] as $date => $missing) {
+             if (count($missing) > 0) {
+               $wrap_up .= "\t$date: " . count($missing) . "\n";
+             }
+           }
+         }
+         $wrap_up .= "Recurring donations: $missing_recurring\n";
+         if ($missing_recurring > 0) {
+           foreach ($remaining['recurring'] as $date => $missing) {
+             if (count($missing) > 0) {
+               $wrap_up .= "\t$date: " . count($missing) . "\n";
+             }
+           }
+         }
+
+         $wrap_up .= "Transaction IDs:\n";
+         foreach ($remaining as $group => $transactions) {
+           foreach ($transactions as $date => $missing) {
+             foreach ($missing as $transaction) {
+               $wrap_up .= "\t" . 
WmfTransaction::from_message($transaction)->get_unique_id() . "\n";
+             }
+           }
+         }
+
+         $wrap_up .= 'Initial stats on recon files: ' . 
print_r($recon_file_stats, true) . "\n";
+       }
+
+       wmf_audit_echo($wrap_up);
+    }
+
+    /**
+     * Returns an array of the full paths to all valid reconciliation files
+     * @return array Full paths to all recon files
+     */
+    protected function get_all_recon_files() {
+      $files_directory = $this->get_recon_dir();
+      //foreach file in the directory, if it matches our pattern, add it to 
the array.
+      $files = array();
+      if ($handle = opendir($files_directory)) {
+       while (( $file = readdir($handle) ) !== false) {
+         if ($this->get_filetype($file) === 'recon') {
+           $filedate = $this->get_recon_file_date( $file ); //which is not the 
same thing as the edited date... probably.
+           $files[$filedate] = $files_directory . $file;
+         }
+       }
+       closedir($handle);
+       ksort($files);
+       return $files;
+      } else {
+       //can't open the directory at all. Problem.
+       wmf_audit_log_error("Can't open directory $files_directory", 
'FILE_DIR_MISSING'); //should be fatal
+      }
+      return false;
+    }
+
+    /**
+     * Go transaction hunting in the payments logs. This is by far the most
+     * processor-intensive part, but we have some timesaving new near-givens 
to work
+     * with.
+     * Things to remember:
+     * The date on the payments log, probably doesn't contain much of that 
actual
+     * date. It's going to be the previous day, mostly.
+     * Also, remember logrotate exists, so it might be the next day before we 
get
+     * the payments log we would be most interested in today.
+     *
+     * @param array $missing_by_date An array of all the missing transactions 
we
+     * have pulled out of the nightlies, indexed by the standard WMF date 
format.
+     * @return mixed An array of transactions we couldn't find or deal with (by
+     * date), or false on error
+     */
+    protected function log_hunt_and_send($missing_by_date) {
+
+      if (empty($missing_by_date)) {
+       wmf_audit_echo(__FUNCTION__ . ': No missing transactions sent to this 
function. Aborting.');
+       return false;
+      }
+
+      ksort($missing_by_date);
+
+      //output the initial counts for each index...
+      $earliest = null;
+      $latest = null;
+      foreach ($missing_by_date as $date => $data) {
+       if (is_null($earliest)) {
+         $earliest = $date;
+       }
+       $latest = $date;
+       wmf_audit_echo($date . " : " . count($data));
+      }
+      wmf_audit_echo("\n");
+
+
+      //REMEMBER: Log date is a liar!
+      //Stepping backwards, log date really means "Now you have all the data 
for
+      //this date, and some from the previous."
+      //Stepping forwards, it means "Now you have all the data from the 
previous
+      //date, and some from the next."
+      //
+      //Come up with the full range of logs to grab
+      //go back the number of days we have configured to search in the past 
for the
+      //current gateway
+      $earliest = wmf_common_date_add_days($earliest, -1 * 
$this->get_log_days_in_past());
+
+      //and add one to the latest to compensate for logrotate... unless that's 
the future.
+      $today = wmf_common_date_get_today_string();
+      $latest = wmf_common_date_add_days($latest, 1);
+      if ($today < $latest) {
+       $latest = $today;
+      }
+
+      //Correct for the date gap function being exclusive on the starting date 
param
+      //More explain above.
+      $earliest -= 1;
+
+      //get the array of all the logs we want to check
+      $logs_to_grab = wmf_common_date_get_date_gap($earliest, $latest);
+
+      if (empty($logs_to_grab)) {
+       wmf_audit_log_error(__FUNCTION__ . ': No logs identified as grabbable. 
Aborting.', 'RUNTIME_ERROR');
+       return false;
+      }
+
+      //want the latest first, from now on.
+      rsort($logs_to_grab);
+      krsort($missing_by_date);
+
+      //Foreach log by date DESC, check all the transactions we are missing 
that might possibly be in this log.
+      //This is going to look a little funny, because the logs are date-named 
and stamped after the day they rotate; Not after the dates for all the data in 
them.
+      //As such, they mostly contain data for the previous day (but not 
exclusively, and not all of it)
+      $tryme = array();
+      foreach ($logs_to_grab as $log_date) {
+       //Add to the pool of what's possible to find in this log, as we step 
backward through the log dates.
+       //If the log date is less than or equal to the date on the transaction
+       //(which may or may not be when it was initiated, but that's the 
past-iest
+       //option), and it hasn't already been added to the pool, add it to the 
pool.
+       //As we're stepping backward, we should look for transactions that come
+       //from the current log date, or the one before.
+       foreach ($missing_by_date as $date => $data) {
+         if ($date >= ($log_date - 1)) {
+           if (!array_key_exists($date, $tryme)) {
+             wmf_audit_echo("Adding date $date to the date pool for log date 
$log_date");
+             $tryme[$date] = $data;
+           }
+         } else {
+           break;
+         }
+       }
+
+       //log something sensible out for what we're about to do
+       $display_dates = array();
+       if (!empty($tryme)) {
+         foreach ($tryme as $date => $thing) {
+           if (count($thing) > 0) {
+             $display_dates[$date] = count($thing);
+           }
+         }
+       }
+       $log = false;
+       if (!empty($display_dates)) {
+         $message = "Checking log $log_date for missing transactions that came 
in with the following dates: ";
+         foreach ($display_dates as $display_date => $local_count) {
+           $message .= "\n\t$display_date : $local_count";
+         }
+         wmf_audit_echo($message);
+
+         //now actually check the log from $log_date, for the missing 
transactions in $tryme
+         // Get the prepped log with the current date, returning false if it's 
not there.
+         $log = $this->get_log_by_date($log_date);
+       }
+
+       if ($log) {
+         //check to see if the missing transactions we're trying now, are in 
there.
+         //Echochar with results for each one.
+         foreach ($tryme as $date => $missing) {
+           if (!empty($missing)) {
+             wmf_audit_echo("Log Date: $log_date: About to check " . 
count($missing) . " missing transactions from $date", true);
+             $checked = 0;
+             $found = 0;
+             foreach ($missing as $id => $transaction) {
+               $checked += 1;
+               //reset vars used below, for extra safety
+               $order_id = false;
+               $data = false;
+               $all_data = false;
+               $contribution_tracking_data = false;
+               $error = false;
+
+               $order_id = $this->get_order_id( $transaction );
+               if (!$order_id) {
+                 $error = array(
+                   'message' => 'Could not get an order id for the following 
transaction ' . print_r($transaction, true),
+                   'code' => 'MISSING_MANDATORY_DATA',
+                 );
+               } else {
+                 //@TODO: If you ever have a gateway that doesn't communicate 
with xml, this is going to have to be abstracted slightly.
+                 //Not going to worry about that right now, though.
+                 $data = $this->get_xml_log_data_by_order_id($order_id, $log);
+               }
+
+               //if we have data at this point, it means we have a match in 
the logs
+               if ($data) {
+                 $found += 1;
+                 $all_data = $this->normalize_and_merge_data( $data, 
$transaction );
+                 if (!$all_data) {
+                   $error = array(
+                     'message' => 'Error normalizing data. Skipping the 
following: ' . print_r($transaction, true) . "\n" . print_r($data, true),
+                     'code' => 'NORMALIZE_DATA',
+                   );
+                 }
+                 if (!$error) {
+                   //lookup contribution_tracking data, and fill it in with 
audit markers if there's nothing there.
+                   $contribution_tracking_data = 
wmf_audit_get_contribution_tracking_data($all_data);
+                 }
+
+                 if (!$contribution_tracking_data) {
+                   $error = array(
+                     'message' => 'No contribution trackin data retrieved for 
transaction ' . print_r($all_data, true),
+                     'code' => 'MISSING_MANDATORY_DATA',
+                   );
+                 }
+
+                 if (!$error) {
+                   //Now that we've made it this far: Easy check to make sure 
we're even looking at the right thing...
+                   //I'm not totally sure this is going to be the right thing 
to do, though. Intended fragility.
+                   if ((!$this->get_runtime_options('fakedb')) &&
+                     
(!empty($contribution_tracking_data['utm_payment_method'])) &&
+                     ($contribution_tracking_data['utm_payment_method'] !== 
$all_data['payment_method'])) {
+
+                     $message = 'Payment method mismatch between utm tracking 
data(' . $contribution_tracking_data['utm_payment_method'];
+                     $message .= ') and normalized log and recon data(' . 
$all_data['payment_method'] . '). Investigation required.';
+                     $error = array(
+                       'message' => $message,
+                       'code' => 'UTM_DATA_MISMATCH',
+                     );
+                   } else {
+                     unset($contribution_tracking_data['utm_payment_method']);
+                     // On the next line, the date field from all_data will 
win, which we totally want.
+                     // I had thought we'd prefer the contribution tracking 
date, but that's just silly.
+                     // However, I'd just like to point out that it would be 
terribly enlightening for some gateways to log the difference...
+                     // ...but not inside the char block, because it'll break 
the pretty.
+                     $all_data = array_merge($contribution_tracking_data, 
$all_data);
+                   }
+
+                   if (!$error) {
+                     //Send to stomp. Or somewhere. Or don't (if it's test 
mode).
+                     wmf_audit_send_transaction($all_data, 'main');
+                     unset($tryme[$date][$id]);
+                     wmf_audit_echo('!');
+                   }
+                 }
+
+               } else {
+                 //no data found in this log, which is expected and normal and 
not a problem.
+                 wmf_audit_echo('.');
+               }
+
+               //End of the transaction search/destroy loop. If we're here and 
have
+               //an error, we found something and the re-fusion didn't work.
+               //Handle consistently, and definitely don't try looking in other
+               //logs.
+               if (is_array($error)) {
+                 wmf_audit_log_error($error['message'], $error['code']);
+                 unset($tryme[$date][$id]);
+                 wmf_audit_echo('X');
+               }
+             }
+             wmf_audit_echo("Log Date: $log_date: Checked $checked missing 
transactions from $date, and found $found\n");
+           }
+         }
+       }
+      }
+
+      //That loop has been stepping back in to the past. So, use what we 
have...
+      wmf_audit_remove_old_logs($log_date, $this->read_working_logs_dir());
+
+      //if we are running in makemissing mode: make the missing transactions.
+      if ($this->get_runtime_options('makemissing')) {
+       $missing_count = wmf_audit_count_missing($tryme);
+       if ($missing_count === 0) {
+         wmf_audit_echo('No further missing transactions to make.');
+       } else {
+         //today minus three. Again: The three is because Shut Up.
+         wmf_audit_echo("Making up to $missing_count missing transactions:");
+         $made = 0;
+         $cutoff = 
wmf_common_date_add_days(wmf_common_date_get_today_string(), -3);
+         foreach ($tryme as $date => $missing) {
+           if ((int) $date <= (int) $cutoff) {
+             foreach ($missing as $id => $message) {
+               $contribution_tracking_data = 
wmf_audit_make_contribution_tracking_data($message);
+               $all_data = array_merge($message, $contribution_tracking_data);
+               $sendme = $this->normalize_partial( $all_data );
+               wmf_audit_send_transaction($sendme, 'main');
+               $made += 1;
+               wmf_audit_echo('!');
+               unset($tryme[$date][$id]);
+             }
+           }
+         }
+         wmf_audit_echo("Made $made missing transactions\n");
+       }
+      }
+
+      return $tryme; //this will contain whatever's left, if we haven't 
errored out at this point
+    }
+
+    /**
+     * Both groom and return a distilled working payments log ready to be 
searched
+     * for missing transaction data
+     * @param string $date The date of the log we want to grab
+     */
+    protected function get_log_by_date($date) {
+      //Could be distilled already.
+      //Could be either in .gz format in the archive
+      //check for the distilled version first
+      //check the local static cache to see if the file we want is available 
in distilled format.
+      static $ready_files = null;
+
+      if (is_null($ready_files)) {
+       $ready_files = $this->read_working_logs_dir();
+      }
+
+      //simple case: It's already ready, or none are ready
+      if (!is_null($ready_files) && array_key_exists($date, $ready_files)) {
+       return $ready_files[$date];
+      }
+
+      //This date is not ready yet. Get the zipped version from the archive, 
unzip
+      //to the working directory, and distill.
+      $compressed_filename = $this->get_compressed_log_file_name( $date );
+      $full_archive_path = wmf_audit_get_log_archive_dir() . 
$compressed_filename;
+      $working_directory = $this->get_working_log_dir();
+      $cleanup = array(); //add files we want to make sure aren't there 
anymore when we're done here.
+      if (file_exists($full_archive_path)) {
+       wmf_audit_echo("Retrieving $full_archive_path");
+       $cmd = "cp $full_archive_path " . $working_directory;
+       exec(escapeshellcmd($cmd), $ret, $errorlevel);
+       if (!file_exists($working_directory . $compressed_filename)) {
+         wmf_audit_log_error("FILE PROBLEM: Trying to get log archives, and 
something went wrong with $cmd", 'FILE_MOVE');
+         return false;
+       } else {
+         $cleanup[] = $working_directory . $compressed_filename;
+       }
+       //uncompress
+       wmf_audit_echo("Gunzipping $working_directory$compressed_filename");
+       $cmd = "gunzip -f $working_directory$compressed_filename";
+       exec(escapeshellcmd($cmd), $ret, $errorlevel);
+       //now check to make sure the file you expect, actually exists
+       $uncompressed_file = $this->get_uncompressed_log_file_name( $date );
+       if (!file_exists($working_directory . $uncompressed_file)) {
+         wmf_audit_log_error("FILE PROBLEM: Something went wrong with 
uncompressing logs: $cmd : $working_directory.$uncompressed_file doesn't 
exist.", 'FILE_UNCOMPRESS');
+       } else {
+         $cleanup[] = $working_directory . $uncompressed_file;
+       }
+
+       //distill & cache locally
+       $distilled_file = $this->get_working_log_file_name( $date );
+       //Can't escape the hard-coded string we're grepping for, because it 
breaks terribly.
+       $cmd = "grep '" . $this->get_log_distilling_grep_string() . "' " . 
escapeshellcmd($working_directory . $uncompressed_file) . " > " . 
escapeshellcmd($working_directory . $distilled_file);
+
+       wmf_audit_echo($cmd);
+       $ret = array();
+       exec($cmd, $ret, $errorlevel);
+       chmod($working_directory . $distilled_file, 0770);
+       $ready_files[$date] = $working_directory . $distilled_file;
+
+       //clean up
+       if (!empty($cleanup)) {
+         foreach ($cleanup as $deleteme) {
+           if (file_exists($deleteme)) {
+             unlink($deleteme);
+           }
+         }
+       }
+
+       //return
+       return $working_directory . $distilled_file;
+      }
+
+      //this happens if the archive file doesn't exist. Definitely not the end 
of the world, but we should probably log about it.
+      wmf_audit_log_error("Archive file $full_archive_path seems not to 
exist\n", 'MISSING_PAYMENTS_LOG');
+      return false;
+    }
+
+    /**
+     * Construct an array of all the distilled working logs we have in the 
working
+     * directory.
+     * @return array Array of date => full path to file for all distilled 
working
+     * logs
+     */
+    protected function read_working_logs_dir() {
+      $working_logs = array();
+      $working_dir = $this->get_working_log_dir();
+      //do the directory read and cache the results in the static
+      if (!$handle = opendir($working_dir)) {
+       die(__FUNCTION__ . ": Can't open directory. We should have noticed 
earlier (in setup_required_directories) that this wasn't going to work. \n");
+      }
+      while (( $file = readdir($handle) ) !== false) {
+       $temp_date = false;
+       if ($this->get_filetype($file) === 'working_log') {
+         $full_path = $working_dir . $file;
+         $temp_date = $this->get_working_log_file_date( $file );
+       }
+       if (!$temp_date) {
+         continue;
+       }
+       $working_logs[$temp_date] = $full_path;
+      }
+      return $working_logs;
+    }
+
+    /**
+     * Figures out what type of file you've got there, according to what the 
gateway
+     * module has defined in its _regex_for_ functions.
+     * @param string $file Full path to the file of interest
+     * @return mixed 
'recon'|'working_log'|'uncompressed_log'|'compressed_log'|false
+     * if it's nothing the gateway recognizes.
+     */
+    protected function get_filetype($file) {
+      //we have three types of files, right? compressed, uncompressed, 
distilled, and recon file.
+      //...four. Four types.
+
+      $types = array(
+       'recon',
+       'working_log',
+       'uncompressed_log',
+       'compressed_log',
+      );
+
+      foreach ($types as $type) {
+       $function_name = 'regex_for_' . $type;
+       // TODO: map rather than functions
+       if (preg_match($this->$function_name(), $file)) {
+         return $type;
+       }
+      }
+
+      return false;
+    }
+
+    /**
+     * Moves recon files to the completed directory. This should probably only 
be
+     * done at the beginning of a run: If we're running in queue flood mode, we
+     * don't know if the data will actually make it all the way in.
+     * @param string $file Full path to the file we want to move off
+     * @return boolean true on success, otherwise false
+     */
+    protected function move_completed_recon_file($file) {
+      $files_directory = $this->get_recon_completed_dir();
+      $completed_dir = $files_directory;
+      if (!is_dir($completed_dir)) {
+       if (!mkdir($completed_dir, 0770)) {
+         $message = "Could not make $completed_dir";
+         wmf_audit_log_error($message, 'FILE_PERMS');
+         return false;
+       }
+      }
+
+      $filename = basename($file);
+      $newfile = $completed_dir . '/' . $filename;
+
+      if (!rename($file, $newfile)) {
+       $message = "Unable to move $file to $newfile";
+
+       wmf_audit_log_error($message, 'FILE_PERMS');
+       return false;
+      } else {
+       chmod($newfile, 0770);
+      }
+      wmf_audit_echo("Moved $file to $newfile");
+      return true;
+    }
+
+    /**
+     * Make sure all the directories we need are there.
+     * @return boolean true on success, otherwise false
+     */
+    protected function setup_required_directories() {
+      $directories = array(
+       'log_archive' => wmf_audit_get_log_archive_dir(),
+       'recon' => $this->get_recon_dir(),
+       'log_working' => $this->get_working_log_dir(),
+       'recon_completed' => $this->get_recon_completed_dir(),
+      );
+
+      foreach ($directories as $id => $dir) {
+       if (!is_dir($dir)) {
+         if ($id === 'log_archive' || $id === 'recon') {
+           //already done. We don't want to try to create these, because 
required files come from here.
+           wmf_audit_log_error("Missing required directory $dir", 
'MISSING_DIR_' . strtoupper($id));
+           return false;
+         }
+
+         if (!mkdir($dir, 0770)) {
+           wmf_audit_log_error("Missing and could not create required 
directory $dir", 'MISSING_DIR_' . strtoupper($id));
+           return false;
+         }
+       }
+      }
+      return true;
+    }
+
+    /**
+     * Check the database to see if we have already recorded the transactions
+     * present in the recon files.
+     * @param array $transactions An array of transactions we have already 
parsed
+     * out from the recon files.
+     * @return mixed An array of transactions that are not in the database 
already,
+     * or false if something goes wrong enough
+     */
+    protected function get_missing_transactions($transactions) {
+      if (empty($transactions)) {
+       wmf_audit_echo(__FUNCTION__ . ': No transactions to find. Returning.');
+       return false;
+      }
+      //go through the transactions and check to see if they're in civi
+      //@TODO: RECURRING. Won't matter for WP initially, though, so I'm 
leaving that for the WX integration phase.
+      $missing = array(
+       'main' => array(),
+       'negative' => array(),
+       'recurring' => array(),
+      );
+      foreach ($transactions as $transaction) {
+       if ($this->record_is_refund( $transaction ) || 
$this->record_is_chargeback( $transaction )) { //negative
+         if ($this->negative_transaction_exists_in_civi( $transaction ) === 
false) {
+           wmf_audit_echo('-'); //add a subtraction. I am the helpfulest 
comment ever.
+           $missing['negative'][] = $transaction;
+         } else {
+           wmf_audit_echo('.');
+         }
+       } else { //normal type
+         if ($this->main_transaction_exists_in_civi( $transaction ) === false) 
{
+           wmf_audit_echo('!');
+           $missing['main'][] = $transaction;
+         } else {
+           wmf_audit_echo('.');
+         }
+       }
+      }
+      return $missing;
+    }
+
+    /**
+     * Visualization helper. Returns the character we want to display for the 
kind
+     * of transaction we have just parsed out of a recon file.
+     * @param array $record A single transaction from a recon file
+     * @return char A single char to display in the char block.
+     */
+    protected function audit_echochar($record) {
+
+      if ($this->record_is_refund( $record )) {
+       return 'r';
+      }
+
+      if ($this->record_is_chargeback( $record )) {
+       return 'b';
+      }
+
+      if ($record['payment_method'] === 'cc') {
+       return 'c';
+      }
+
+      echo print_r($record, true);
+      die(__FUNCTION__ . " Not cc...");
+    }
+
+    /**
+     * Returns supplemental data for the relevant order id from the specified
+     * payments log, if there is any in there. This is the only sure place we 
can
+     * catch data we didn't save, as we're basically grepping for the exact 
data we
+     * sent to the gateway.
+     * It should be noted that this function makes no attempt to normalize the 
data.
+     * If this log doesn't contain data for the order_id in question, return 
false.
+     * @param string $order_id The order id (transaction id) of the missing 
payment
+     * @param string $log The full path to the log we want to search
+     * @return array|boolean The data we sent to the gateway for that order 
id, or
+     * false if we can't find it there.
+     */
+    protected function get_xml_log_data_by_order_id($order_id, $log) {
+      if (!$order_id) {
+       return false;
+      }
+
+      $cmd = 'grep ' . $this->get_log_line_grep_string( $order_id ) . ' ' . 
$log;
+      wmf_audit_echo(__FUNCTION__ . ' ' . $cmd, true);
+
+      $ret = array();
+      exec(escapeshellcmd($cmd), $ret, $errorlevel);
+
+      if (count($ret) > 0) {
+
+       //In this wonderful new world, we only expect one line.
+       if (count($ret) > 1) {
+         wmf_audit_echo("Odd: More than one logline returned for $order_id. 
Investigation Required.");
+       }
+
+       //just take the last one, just in case somebody did manage to do a 
duplicate.
+       $line = $ret[count($ret) - 1];
+       // $linedata for *everything* from payments goes Month, day, time, box, 
bucket, CTID:OID, absolute madness with lots of unpredictable spaces.
+       $linedata = explode(' ', $line);
+       $contribution_id = explode(':', $linedata[5]);
+       $contribution_id = $contribution_id[0];
+
+
+       //look for the raw xml
+       $full_xml = false;
+       $node = $this->get_log_line_xml_outermost_node();
+       $xmlstart = strpos($line, '<?xml');
+       if ($xmlstart === false) {
+         $xmlstart = strpos($line, "<$node>");
+       }
+       $xmlend = strpos($line, "</$node>");
+       if ($xmlend) {
+         $full_xml = true;
+         $xmlend += (strlen($node) + 3);
+         $xml = substr($line, $xmlstart, $xmlend - $xmlstart);
+       } else {
+         //this is a broken line, and it won't load... but we can still parse 
what's left of the thing, the slow way.
+         $xml = substr($line, $xmlstart);
+       }
+       // Syslog wart.  Other control characters should be encoded normally.
+       $xml = str_replace( '#012', "\n", $xml );
+
+       $donor_data = array();
+
+       if ($full_xml) {
+         $xmlobj = new DomDocument;
+         $xmlobj->loadXML($xml);
+
+         $parent_nodes = $this->get_log_line_xml_parent_nodes();
+
+         if (empty($parent_nodes)) {
+           wmf_audit_log_error(__FUNCTION__ . ': No parent nodes defined. Can 
not continue.', 'RUNTIME_ERROR');
+           die("Stop dying here");
+         }
+
+         foreach ($parent_nodes as $parent_node) {
+           foreach ($xmlobj->getElementsByTagName($parent_node) as $node) {
+             foreach ($node->childNodes as $childnode) {
+               if (trim($childnode->nodeValue) != '') {
+                 $donor_data[$childnode->nodeName] = $childnode->nodeValue;
+               }
+             }
+           }
+         }
+       } else {
+         //the XML got cut off prematurely, probably because syslog was set up 
to truncate on payments.
+         //rebuild what we can the old-fashioned way.
+         $search_for_nodes = $this->get_log_line_xml_data_nodes();
+
+         if (empty($search_for_nodes)) {
+           wmf_audit_log_error(__FUNCTION__ . ': No searchable nodes defined. 
Can not continue.', 'RUNTIME_ERROR');
+           die("Stop dying here");
+         }
+         foreach ($search_for_nodes as $node) {
+           $tmp = wmf_audit_get_partial_xml_node_value($node, $xml);
+           if (!is_null($tmp)) {
+             $donor_data[$node] = $tmp;
+           }
+         }
+       }
+
+       if (!empty($donor_data)) {
+         $donor_data['contribution_tracking_id'] = $contribution_id;
+         return $donor_data;
+       } else {
+         wmf_audit_log_error("We found a transaction in the logs for 
$order_id, but there's nothing left after we tried to grab its data. 
Investigation required.", 'DATA_WEIRD');
+       }
+      }
+      return false; //no big deal, it just wasn't there. This will happen most 
of the time.
+    }
+}
diff --git a/sites/all/modules/wmf_audit/wmf_audit.info 
b/sites/all/modules/wmf_audit/wmf_audit.info
index d82630a..3c1b3d5 100644
--- a/sites/all/modules/wmf_audit/wmf_audit.info
+++ b/sites/all/modules/wmf_audit/wmf_audit.info
@@ -7,3 +7,4 @@
 dependencies[] = wmf_civicrm
 package = WMF Audit
 configure = admin/config/wmf_audit
+files[] = BaseAuditProcessor.php
diff --git a/sites/all/modules/wmf_audit/wmf_audit.module 
b/sites/all/modules/wmf_audit/wmf_audit.module
index adff527..f40704c 100644
--- a/sites/all/modules/wmf_audit/wmf_audit.module
+++ b/sites/all/modules/wmf_audit/wmf_audit.module
@@ -65,194 +65,6 @@
 }
 
 /**
- * The main function, intended to be called straight from the drush command and
- * nowhere else.
- * @param string $submod_prefix The prefix for the gateway-specific submodule
- * for which we are running the nightly audit. For any of this to work, there
- * will have to be a submodule called $submod_prefix . '_audit'.
- * @param array $drush_params An array of the drush parameters that were set at
- * the command line.
- * @return boolean
- */
-function wmf_audit_main($submod_prefix, $drush_params) {
-  civicrm_initialize();
-
-  //this is more of a runtime param... maybe set it on the outside? But no, I
-  //kind of like that this one thing is explicitly required in an obvious way 
to
-  //even call the function.
-  $drush_params['submod_prefix'] = $submod_prefix;
-
-  //This function is weird. Because we're passing in an array, it's setting the
-  //runtime params here.
-  //Everywhere else, it will work as a get function.
-  wmf_audit_runtime_options($drush_params);
-
-
-  //make sure all the things we need are there.
-  if (!wmf_audit_setup_required_directories()) {
-    return false;
-  }
-
-  //find out what the situation is with the available recon files, by date
-  $recon_files = wmf_audit_get_all_recon_files();
-
-  //get missing transactions from one or more recon files
-  //let's just assume that the default mode will be to pop off the top three 
(most recent) at this point. :)
-  //...Three, because Shut Up.
-  $count = count($recon_files);
-  if ($count > 3 && !wmf_audit_runtime_options('run_all')) {
-    $count = 3;
-  }
-
-  $total_missing = array();
-  $recon_file_stats = array();
-
-  for ($i = 0; $i < $count; ++$i) {
-    $parsed = array();
-    $missing = array();
-
-    //parce the recon files into something relatively reasonable.
-    $file = array_pop($recon_files);
-    wmf_audit_echo("Parsing $file");
-    $start_time = microtime(true);
-    $parsed = wmfa_execute('parse_recon_file', array('file' => $file));
-    $time = microtime(true) - $start_time;
-    wmf_audit_echo(count($parsed) . " results found in $time seconds\n");
-
-    //remove transactions we already know about
-    $start_time = microtime(true);
-    $missing = wmf_audit_get_missing_transactions($parsed);
-    $recon_file_stats[$file] = wmf_audit_count_missing($missing);
-    $time = microtime(true) - $start_time;
-    wmf_audit_echo(wmf_audit_count_missing($missing) . ' missing transactions 
(of a possible ' . count($parsed) . ") identified in $time seconds\n");
-
-    //If the file is empty, move it off.
-    // Note that we are not archiving files that have missing transactions,
-    // which might be resolved below.  Those are archived on the next run,
-    // once we can confirm they have hit Civi and are no longer missing.
-    if (wmf_audit_count_missing($missing) <= 
wmf_audit_runtime_options('recon_complete_count')) {
-      wmf_audit_move_completed_recon_file($file);
-    }
-
-    //grumble...
-    if (!empty($missing)) {
-      foreach ($missing as $type => $data) {
-        if (!empty($missing[$type])) {
-          if (array_key_exists($type, $total_missing)) {
-            $total_missing[$type] = array_merge($total_missing[$type], 
$missing[$type]);
-          } else {
-            $total_missing[$type] = $missing[$type];
-          }
-        }
-      }
-    }
-  }
-  $total_missing_count = wmf_audit_count_missing($total_missing);
-  wmf_audit_echo("$total_missing_count total missing transactions identified 
at start");
-
-  //get the date distribution on what's left... for ***main transactions 
only***
-  //That should be to say: The things that are totally in the payments logs.
-  //Other things, we will have to look other places, or just rebuild.
-  $missing_by_date = array();
-  if (array_key_exists('main', $total_missing) && 
!empty($total_missing['main'])) {
-    foreach ($total_missing['main'] as $record) {
-      $missing_by_date[wmfa_execute('get_record_human_date', array('record' => 
$record))][] = $record;
-    }
-  }
-
-
-  $remaining = null;
-  if (!empty($missing_by_date)) {
-    $remaining['main'] = wmf_audit_log_hunt_and_send($missing_by_date);
-  }
-
-  //@TODO: Handle the recurring type, once we have a gateway that gives some 
of those to us.
-  //
-  //Handle the negatives now. That way, the parent transactions will probably 
exist.
-  wmf_audit_echo("Processing 'negative' transactions");
-  $neg_count = 0;
-
-  if (array_key_exists('negative', $total_missing) && 
!empty($total_missing['negative'])) {
-    foreach ($total_missing['negative'] as $record) {
-      //check to see if the parent exists. If it does, normalize and send.
-      if (worldpay_audit_main_transaction_exists_in_civi($record)) {
-        $normal = wmfa_execute('normalize_negative', array('record' => 
$record));
-        if (wmf_audit_send_transaction($normal, 'negative')) {
-          $neg_count += 1;
-          wmf_audit_echo('!');
-        }
-        wmf_audit_echo('X');
-      } else {
-        //@TODO: Some version of makemissing should make these, too. Gar.
-        $remaining['negative'][wmfa_execute('get_record_human_date', 
array('record' => $record))][] = $record;
-        wmf_audit_echo('.');
-      }
-    }
-    wmf_audit_echo("Processed $neg_count 'negative' transactions\n");
-  }
-
-  //Wrap it up and put a bow on it.
-  //@TODO much later: Make a fredge table for these things and dump some 
messages over there about what we just did.
-  $missing_main = 0;
-  $missing_negative = 0;
-  $missing_recurring = 0;
-  $missing_at_end = 0;
-  if (is_array($remaining) && !empty($remaining)) {
-    foreach ($remaining as $type => $data) {
-      $count = wmf_audit_count_missing($data);
-      ${'missing_' . $type} = $count;
-      $missing_at_end += $count;
-    }
-  }
-
-  $wrap_up = "\nDone! Final stats:\n";
-  $wrap_up .= "Total missing at start: $total_missing_count\n";
-  $wrap_up .= 'Missing at end: ' . $missing_at_end . "\n\n";
-
-  if ($missing_at_end > 0) {
-    $wrap_up .= "Missing transaction summary:\n";
-    $wrap_up .= "Regular donations: $missing_main\n";
-    if ($missing_main > 0) {
-      foreach ($remaining['main'] as $date => $missing) {
-        if (count($missing) > 0) {
-          $wrap_up .= "\t$date: " . count($missing) . "\n";
-        }
-      }
-    }
-    $wrap_up .= "Refunds and chargebacks: $missing_negative\n";
-    if ($missing_negative > 0) {
-      foreach ($remaining['negative'] as $date => $missing) {
-        if (count($missing) > 0) {
-          $wrap_up .= "\t$date: " . count($missing) . "\n";
-        }
-      }
-    }
-    $wrap_up .= "Recurring donations: $missing_recurring\n";
-    if ($missing_recurring > 0) {
-      foreach ($remaining['recurring'] as $date => $missing) {
-        if (count($missing) > 0) {
-          $wrap_up .= "\t$date: " . count($missing) . "\n";
-        }
-      }
-    }
-
-    $wrap_up .= "Transaction IDs:\n";
-    foreach ($remaining as $group => $transactions) {
-      foreach ($transactions as $date => $missing) {
-        foreach ($missing as $transaction) {
-          $wrap_up .= "\t" . 
WmfTransaction::from_message($transaction)->get_unique_id() . "\n";
-        }
-      }
-    }
-
-    $wrap_up .= 'Initial stats on recon files: ' . print_r($recon_file_stats, 
true) . "\n";
-  }
-
-  wmf_audit_echo($wrap_up);
-  return true;
-}
-
-/**
  * Counts the missing transactions in the main array of missing transactions.
  * This is annoying and needed its own function, because the $missing array 
goes
  * $missing[$type]=> array of transactions.
@@ -273,366 +85,13 @@
 }
 
 /**
- * Returns an array of the full paths to all valid reconciliation files
- * @return array Full paths to all recon files
- */
-function wmf_audit_get_all_recon_files() {
-  $files_directory = wmfa_execute('get_recon_dir');
-  //foreach file in the directory, if it matches our pattern, add it to the 
array.
-  $files = array();
-  if ($handle = opendir($files_directory)) {
-    while (( $file = readdir($handle) ) !== false) {
-      if (wmf_audit_get_filetype($file) === 'recon') {
-        $filedate = wmfa_execute('get_recon_file_date', array($file)); //which 
is not the same thing as the edited date... probably.
-        $files[$filedate] = $files_directory . $file;
-      }
-    }
-    closedir($handle);
-    ksort($files);
-    return $files;
-  } else {
-    //can't open the directory at all. Problem.
-    wmf_audit_log_error("Can't open directory $files_directory", 
'FILE_DIR_MISSING'); //should be fatal
-  }
-  return false;
-}
-
-/**
- * Go transaction hunting in the payments logs. This is by far the most
- * processor-intensive part, but we have some timesaving new near-givens to 
work
- * with.
- * Things to remember:
- * The date on the payments log, probably doesn't contain much of that actual
- * date. It's going to be the previous day, mostly.
- * Also, remember logrotate exists, so it might be the next day before we get
- * the payments log we would be most interested in today.
- *
- * @param array $missing_by_date An array of all the missing transactions we
- * have pulled out of the nightlies, indexed by the standard WMF date format.
- * @return mixed An array of transactions we couldn't find or deal with (by
- * date), or false on error
- */
-function wmf_audit_log_hunt_and_send($missing_by_date) {
-
-  if (empty($missing_by_date)) {
-    wmf_audit_echo(__FUNCTION__ . ': No missing transactions sent to this 
function. Aborting.');
-    return false;
-  }
-
-  ksort($missing_by_date);
-
-  //output the initial counts for each index...
-  $earliest = null;
-  $latest = null;
-  foreach ($missing_by_date as $date => $data) {
-    if (is_null($earliest)) {
-      $earliest = $date;
-    }
-    $latest = $date;
-    wmf_audit_echo($date . " : " . count($data));
-  }
-  wmf_audit_echo("\n");
-
-
-  //REMEMBER: Log date is a liar!
-  //Stepping backwards, log date really means "Now you have all the data for
-  //this date, and some from the previous."
-  //Stepping forwards, it means "Now you have all the data from the previous
-  //date, and some from the next."
-  //
-  //Come up with the full range of logs to grab
-  //go back the number of days we have configured to search in the past for the
-  //current gateway
-  $earliest = wmf_common_date_add_days($earliest, -1 * 
wmfa_execute('get_log_days_in_past'));
-
-  //and add one to the latest to compensate for logrotate... unless that's the 
future.
-  $today = wmf_common_date_get_today_string();
-  $latest = wmf_common_date_add_days($latest, 1);
-  if ($today < $latest) {
-    $latest = $today;
-  }
-
-  //Correct for the date gap function being exclusive on the starting date 
param
-  //More explain above.
-  $earliest -= 1;
-
-  //get the array of all the logs we want to check
-  $logs_to_grab = wmf_common_date_get_date_gap($earliest, $latest);
-
-  if (empty($logs_to_grab)) {
-    wmf_audit_log_error(__FUNCTION__ . ': No logs identified as grabbable. 
Aborting.', 'RUNTIME_ERROR');
-    return false;
-  }
-
-  //want the latest first, from now on.
-  rsort($logs_to_grab);
-  krsort($missing_by_date);
-
-  //Foreach log by date DESC, check all the transactions we are missing that 
might possibly be in this log.
-  //This is going to look a little funny, because the logs are date-named and 
stamped after the day they rotate; Not after the dates for all the data in them.
-  //As such, they mostly contain data for the previous day (but not 
exclusively, and not all of it)
-  $tryme = array();
-  foreach ($logs_to_grab as $log_date) {
-    //Add to the pool of what's possible to find in this log, as we step 
backward through the log dates.
-    //If the log date is less than or equal to the date on the transaction
-    //(which may or may not be when it was initiated, but that's the past-iest
-    //option), and it hasn't already been added to the pool, add it to the 
pool.
-    //As we're stepping backward, we should look for transactions that come
-    //from the current log date, or the one before.
-    foreach ($missing_by_date as $date => $data) {
-      if ($date >= ($log_date - 1)) {
-        if (!array_key_exists($date, $tryme)) {
-          wmf_audit_echo("Adding date $date to the date pool for log date 
$log_date");
-          $tryme[$date] = $data;
-        }
-      } else {
-        break;
-      }
-    }
-
-    //log something sensible out for what we're about to do
-    $display_dates = array();
-    if (!empty($tryme)) {
-      foreach ($tryme as $date => $thing) {
-        if (count($thing) > 0) {
-          $display_dates[$date] = count($thing);
-        }
-      }
-    }
-    $log = false;
-    if (!empty($display_dates)) {
-      $message = "Checking log $log_date for missing transactions that came in 
with the following dates: ";
-      foreach ($display_dates as $display_date => $local_count) {
-        $message .= "\n\t$display_date : $local_count";
-      }
-      wmf_audit_echo($message);
-
-      //now actually check the log from $log_date, for the missing 
transactions in $tryme
-      // Get the prepped log with the current date, returning false if it's 
not there.
-      $log = wmf_audit_get_log_by_date($log_date);
-    }
-
-    if ($log) {
-      //check to see if the missing transactions we're trying now, are in 
there.
-      //Echochar with results for each one.
-      foreach ($tryme as $date => $missing) {
-        if (!empty($missing)) {
-          wmf_audit_echo("Log Date: $log_date: About to check " . 
count($missing) . " missing transactions from $date", true);
-          $checked = 0;
-          $found = 0;
-          foreach ($missing as $id => $transaction) {
-            $checked += 1;
-            //reset vars used below, for extra safety
-            $order_id = false;
-            $data = false;
-            $all_data = false;
-            $contribution_tracking_data = false;
-            $error = false;
-
-            $order_id = wmfa_execute('get_order_id', array('transaction' => 
$transaction));
-            if (!$order_id) {
-              $error = array(
-                'message' => 'Could not get an order id for the following 
transaction ' . print_r($transaction, true),
-                'code' => 'MISSING_MANDATORY_DATA',
-              );
-            } else {
-              //@TODO: If you ever have a gateway that doesn't communicate 
with xml, this is going to have to be abstracted slightly.
-              //Not going to worry about that right now, though.
-              $data = wmf_audit_get_xml_log_data_by_order_id($order_id, $log);
-            }
-
-            //if we have data at this point, it means we have a match in the 
logs
-            if ($data) {
-              $found += 1;
-              $all_data = wmfa_execute('normalize_and_merge_data', 
array('xml_data' => $data, 'recon_data' => $transaction));
-              if (!$all_data) {
-                $error = array(
-                  'message' => 'Error normalizing data. Skipping the 
following: ' . print_r($transaction, true) . "\n" . print_r($data, true),
-                  'code' => 'NORMALIZE_DATA',
-                );
-              }
-              if (!$error) {
-                //lookup contribution_tracking data, and fill it in with audit 
markers if there's nothing there.
-                $contribution_tracking_data = 
wmf_audit_get_contribution_tracking_data($all_data);
-              }
-
-              if (!$contribution_tracking_data) {
-                $error = array(
-                  'message' => 'No contribution trackin data retrieved for 
transaction ' . print_r($all_data, true),
-                  'code' => 'MISSING_MANDATORY_DATA',
-                );
-              }
-
-              if (!$error) {
-                //Now that we've made it this far: Easy check to make sure 
we're even looking at the right thing...
-                //I'm not totally sure this is going to be the right thing to 
do, though. Intended fragility.
-                if ((!wmf_audit_runtime_options('fakedb')) &&
-                  (!empty($contribution_tracking_data['utm_payment_method'])) 
&&
-                  ($contribution_tracking_data['utm_payment_method'] !== 
$all_data['payment_method'])) {
-
-                  $message = 'Payment method mismatch between utm tracking 
data(' . $contribution_tracking_data['utm_payment_method'];
-                  $message .= ') and normalized log and recon data(' . 
$all_data['payment_method'] . '). Investigation required.';
-                  $error = array(
-                    'message' => $message,
-                    'code' => 'UTM_DATA_MISMATCH',
-                  );
-                } else {
-                  unset($contribution_tracking_data['utm_payment_method']);
-                  // On the next line, the date field from all_data will win, 
which we totally want.
-                  // I had thought we'd prefer the contribution tracking date, 
but that's just silly.
-                  // However, I'd just like to point out that it would be 
terribly enlightening for some gateways to log the difference...
-                  // ...but not inside the char block, because it'll break the 
pretty.
-                  $all_data = array_merge($contribution_tracking_data, 
$all_data);
-                }
-
-                if (!$error) {
-                  //Send to stomp. Or somewhere. Or don't (if it's test mode).
-                  wmf_audit_send_transaction($all_data, 'main');
-                  unset($tryme[$date][$id]);
-                  wmf_audit_echo('!');
-                }
-              }
-
-            } else {
-              //no data found in this log, which is expected and normal and 
not a problem.
-              wmf_audit_echo('.');
-            }
-
-            //End of the transaction search/destroy loop. If we're here and 
have
-            //an error, we found something and the re-fusion didn't work.
-            //Handle consistently, and definitely don't try looking in other
-            //logs.
-            if (is_array($error)) {
-              wmf_audit_log_error($error['message'], $error['code']);
-              unset($tryme[$date][$id]);
-              wmf_audit_echo('X');
-            }
-          }
-          wmf_audit_echo("Log Date: $log_date: Checked $checked missing 
transactions from $date, and found $found\n");
-        }
-      }
-    }
-  }
-
-  //That loop has been stepping back in to the past. So, use what we have...
-  wmf_audit_remove_old_logs($log_date);
-
-  //if we are running in makemissing mode: make the missing transactions.
-  if (wmf_audit_runtime_options('makemissing')) {
-    $missing_count = wmf_audit_count_missing($tryme);
-    if ($missing_count === 0) {
-      wmf_audit_echo('No further missing transactions to make.');
-    } else {
-      //today minus three. Again: The three is because Shut Up.
-      wmf_audit_echo("Making up to $missing_count missing transactions:");
-      $made = 0;
-      $cutoff = wmf_common_date_add_days(wmf_common_date_get_today_string(), 
-3);
-      foreach ($tryme as $date => $missing) {
-        if ((int) $date <= (int) $cutoff) {
-          foreach ($missing as $id => $message) {
-            $contribution_tracking_data = 
wmf_audit_make_contribution_tracking_data($message);
-            $all_data = array_merge($message, $contribution_tracking_data);
-            $sendme = wmfa_execute('normalize_partial', array('record' => 
$all_data));
-            wmf_audit_send_transaction($sendme, 'main');
-            $made += 1;
-            wmf_audit_echo('!');
-            unset($tryme[$date][$id]);
-          }
-        }
-      }
-      wmf_audit_echo("Made $made missing transactions\n");
-    }
-  }
-
-  return $tryme; //this will contain whatever's left, if we haven't errored 
out at this point
-}
-
-/**
- * Both groom and return a distilled working payments log ready to be searched
- * for missing transaction data
- * @param string $date The date of the log we want to grab
- */
-function wmf_audit_get_log_by_date($date) {
-  //Could be distilled already.
-  //Could be either in .gz format in the archive
-  //check for the distilled version first
-  //check the local static cache to see if the file we want is available in 
distilled format.
-  static $ready_files = null;
-
-  if (is_null($ready_files)) {
-    $ready_files = wmf_audit_read_working_logs_dir();
-  }
-
-  //simple case: It's already ready, or none are ready
-  if (!is_null($ready_files) && array_key_exists($date, $ready_files)) {
-    return $ready_files[$date];
-  }
-
-  //This date is not ready yet. Get the zipped version from the archive, unzip
-  //to the working directory, and distill.
-  $compressed_filename = wmfa_execute('get_compressed_log_file_name', 
array('date' => $date));
-  $full_archive_path = wmf_audit_get_log_archive_dir() . $compressed_filename;
-  $working_directory = wmfa_execute('get_working_log_dir');
-  $cleanup = array(); //add files we want to make sure aren't there anymore 
when we're done here.
-  if (file_exists($full_archive_path)) {
-    wmf_audit_echo("Retrieving $full_archive_path");
-    $cmd = "cp $full_archive_path " . $working_directory;
-    exec(escapeshellcmd($cmd), $ret, $errorlevel);
-    if (!file_exists($working_directory . $compressed_filename)) {
-      wmf_audit_log_error("FILE PROBLEM: Trying to get log archives, and 
something went wrong with $cmd", 'FILE_MOVE');
-      return false;
-    } else {
-      $cleanup[] = $working_directory . $compressed_filename;
-    }
-    //uncompress
-    wmf_audit_echo("Gunzipping $working_directory$compressed_filename");
-    $cmd = "gunzip -f $working_directory$compressed_filename";
-    exec(escapeshellcmd($cmd), $ret, $errorlevel);
-    //now check to make sure the file you expect, actually exists
-    $uncompressed_file = wmfa_execute('get_uncompressed_log_file_name', 
array('date' => $date));
-    if (!file_exists($working_directory . $uncompressed_file)) {
-      wmf_audit_log_error("FILE PROBLEM: Something went wrong with 
uncompressing logs: $cmd : $working_directory.$uncompressed_file doesn't 
exist.", 'FILE_UNCOMPRESS');
-    } else {
-      $cleanup[] = $working_directory . $uncompressed_file;
-    }
-
-    //distill & cache locally
-    $distilled_file = wmfa_execute('get_working_log_file_name', array('date' 
=> $date));
-    //Can't escape the hard-coded string we're grepping for, because it breaks 
terribly.
-    $cmd = "grep '" . wmfa_execute('get_log_distilling_grep_string') . "' " . 
escapeshellcmd($working_directory . $uncompressed_file) . " > " . 
escapeshellcmd($working_directory . $distilled_file);
-
-    wmf_audit_echo($cmd);
-    $ret = array();
-    exec($cmd, $ret, $errorlevel);
-    chmod($working_directory . $distilled_file, 0770);
-    $ready_files[$date] = $working_directory . $distilled_file;
-
-    //clean up
-    if (!empty($cleanup)) {
-      foreach ($cleanup as $deleteme) {
-        if (file_exists($deleteme)) {
-          unlink($deleteme);
-        }
-      }
-    }
-
-    //return
-    return $working_directory . $distilled_file;
-  }
-
-  //this happens if the archive file doesn't exist. Definitely not the end of 
the world, but we should probably log about it.
-  wmf_audit_log_error("Archive file $full_archive_path seems not to exist\n", 
'MISSING_PAYMENTS_LOG');
-  return false;
-}
-
-/**
  * Remove all distilled logs older than the oldest date ($date)
  * Not even a big deal if we overshoot and remove too many, because we'll just
  * remake them next time if they're missing.
  * @param string $date The date string for the oldest log we want to keep
+ * @param string $working_logs path to working logs directory
  */
-function wmf_audit_remove_old_logs($date) {
-  $working_logs = wmf_audit_read_working_logs_dir();
+function wmf_audit_remove_old_logs($date, $working_logs) {
   if (!empty($working_logs)) {
     foreach ($working_logs as $logdate => $file) {
       if ((int) $logdate < (int) $date) {
@@ -643,70 +102,14 @@
 }
 
 /**
- * Construct an array of all the distilled working logs we have in the working
- * directory.
- * @return array Array of date => full path to file for all distilled working
- * logs
- */
-function wmf_audit_read_working_logs_dir() {
-  $working_logs = array();
-  $working_dir = wmfa_execute('get_working_log_dir');
-  //do the directory read and cache the results in the static
-  if (!$handle = opendir($working_dir)) {
-    die(__FUNCTION__ . ": Can't open directory. We should have noticed earlier 
(in setup_required_directories) that this wasn't going to work. \n");
-  }
-  while (( $file = readdir($handle) ) !== false) {
-    $temp_date = false;
-    if (wmf_audit_get_filetype($file) === 'working_log') {
-      $full_path = $working_dir . $file;
-      $temp_date = wmfa_execute('get_working_log_file_date', array('file' => 
$file)); //($file);
-    }
-    if (!$temp_date) {
-      continue;
-    }
-    $working_logs[$temp_date] = $full_path;
-  }
-  return $working_logs;
-}
-
-/**
- * Figures out what type of file you've got there, according to what the 
gateway
- * module has defined in its _regex_for_ functions.
- * @param string $file Full path to the file of interest
- * @return mixed 
'recon'|'working_log'|'uncompressed_log'|'compressed_log'|false
- * if it's nothing the gateway recognizes.
- */
-function wmf_audit_get_filetype($file) {
-  //we have three types of files, right? compressed, uncompressed, distilled, 
and recon file.
-  //...four. Four types.
-
-  $types = array(
-    'recon',
-    'working_log',
-    'uncompressed_log',
-    'compressed_log',
-  );
-
-  foreach ($types as $type) {
-    $function_name = 'regex_for_' . $type;
-    if (preg_match(wmfa_execute($function_name), $file)) {
-      return $type;
-    }
-  }
-
-  return false;
-}
-
-/**
  * Moves recon files to the completed directory. This should probably only be
  * done at the beginning of a run: If we're running in queue flood mode, we
  * don't know if the data will actually make it all the way in.
  * @param string $file Full path to the file we want to move off
+ * @param string $completed_dir Path to the recon completed directory.
  * @return boolean true on success, otherwise false
  */
-function wmf_audit_move_completed_recon_file($file) {
-  $files_directory = wmfa_execute('get_recon_completed_dir');
-  $completed_dir = $files_directory;
+function wmf_audit_move_completed_recon_file($file, $completed_dir) {
   if (!is_dir($completed_dir)) {
     if (!mkdir($completed_dir, 0770)) {
       $message = "Could not make $completed_dir";
@@ -728,121 +131,6 @@
   }
   wmf_audit_echo("Moved $file to $newfile");
   return true;
-}
-
-/**
- * Make sure all the directories we need are there.
- * @return boolean true on success, otherwise false
- */
-function wmf_audit_setup_required_directories() {
-  $directories = array(
-    'log_archive' => wmf_audit_get_log_archive_dir(),
-    'recon' => wmfa_execute('get_recon_dir'),
-    'log_working' => wmfa_execute('get_working_log_dir'),
-    'recon_completed' => wmfa_execute('get_recon_completed_dir'),
-  );
-
-  foreach ($directories as $id => $dir) {
-    if (!is_dir($dir)) {
-      if ($id === 'log_archive' || $id === 'recon') {
-        //already done. We don't want to try to create these, because required 
files come from here.
-        wmf_audit_log_error("Missing required directory $dir", 'MISSING_DIR_' 
. strtoupper($id));
-        return false;
-      }
-
-      if (!mkdir($dir, 0770)) {
-        wmf_audit_log_error("Missing and could not create required directory 
$dir", 'MISSING_DIR_' . strtoupper($id));
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-/**
- * Executes a function that is expected to exist in the relevant submodule
- * defined at runtime. Fails angrily if that function doesn't exist.
- * @param string $function_name_base Everything after the submodule . '_audit_'
- * in the function you're trying to call.
- * @param array $parameters The parameters you want to send to the function, in
- * array format.
- * @return mixed The return value of the specified function.
- */
-function wmfa_execute($function_name_base, $parameters = array()) {
-  $submod_prefix = wmf_audit_runtime_options('submod_prefix');
-  $function_name = $submod_prefix . '_audit_' . $function_name_base;
-  if (function_exists($function_name)) {
-    return call_user_func_array($function_name, $parameters);
-  } else {
-    //serious issues, you have.
-    wmf_audit_log_error("Missing required function $function_name", 
'MISSING_FUNCTION'); //should be fatal
-    die(); //this really should die here. For real, though.
-  }
-  return false; //no way to get here with all the dying
-}
-
-/**
- * Check the database to see if we have already recorded the transactions
- * present in the recon files.
- * @param array $transactions An array of transactions we have already parsed
- * out from the recon files.
- * @return mixed An array of transactions that are not in the database already,
- * or false if something goes wrong enough
- */
-function wmf_audit_get_missing_transactions($transactions) {
-  if (empty($transactions)) {
-    wmf_audit_echo(__FUNCTION__ . ': No transactions to find. Returning.');
-    return false;
-  }
-  //go through the transactions and check to see if they're in civi
-  //@TODO: RECURRING. Won't matter for WP initially, though, so I'm leaving 
that for the WX integration phase.
-  $missing = array(
-    'main' => array(),
-    'negative' => array(),
-    'recurring' => array(),
-  );
-  foreach ($transactions as $transaction) {
-    if (wmfa_execute('record_is_refund', array('record' => $transaction)) || 
wmfa_execute('record_is_chargeback', array('record' => $transaction))) { 
//negative
-      if (wmfa_execute('negative_transaction_exists_in_civi', 
array('transaction' => $transaction)) === false) {
-        wmf_audit_echo('-'); //add a subtraction. I am the helpfulest comment 
ever.
-        $missing['negative'][] = $transaction;
-      } else {
-        wmf_audit_echo('.');
-      }
-    } else { //normal type
-      if (wmfa_execute('main_transaction_exists_in_civi', array('transaction' 
=> $transaction)) === false) {
-        wmf_audit_echo('!');
-        $missing['main'][] = $transaction;
-      } else {
-        wmf_audit_echo('.');
-      }
-    }
-  }
-  return $missing;
-}
-
-/**
- * Visualization helper. Returns the character we want to display for the kind
- * of transaction we have just parsed out of a recon file.
- * @param array $record A single transaction from a recon file
- * @return char A single char to display in the char block.
- */
-function wmf_audit_echochar($record) {
-
-  if (wmfa_execute('record_is_refund', array('record' => $record))) {
-    return 'r';
-  }
-
-  if (wmfa_execute('record_is_chargeback', array('record' => $record))) {
-    return 'b';
-  }
-
-  if ($record['payment_method'] === 'cc') {
-    return 'c';
-  }
-
-  echo print_r($record, true);
-  die(__FUNCTION__ . " Not cc...");
 }
 
 /**
@@ -892,6 +180,7 @@
  * A confusing function for holding local runtime vars without having to goof
  * around with globals. Works as both get and set, depending on what you pass 
in
  * with $confusing_thing
+ * TODO: Stop that.  Some vars should be global, most should be private to the 
audit class.
  * @staticvar array $args The arguments set at runtime via drush command
  * @param array|string|null $confusing_thing A confusing thing.
  * * When it is an array, sets the internal static $args variable to that array
@@ -936,6 +225,7 @@
 /**
  * Returns an array of errors that should not cause the script to blow up, but
  * which will probably still cause messages to get thrown out.
+ * TODO: Use WmfException instead.
  * @return boolean true if the error code is fatal, otherwise false.
  */
 function wmf_audit_error_isfatal($error) {
@@ -958,113 +248,6 @@
   } else {
     return true;
   }
-}
-
-
-/**
- * Returns supplemental data for the relevant order id from the specified
- * payments log, if there is any in there. This is the only sure place we can
- * catch data we didn't save, as we're basically grepping for the exact data we
- * sent to the gateway.
- * It should be noted that this function makes no attempt to normalize the 
data.
- * If this log doesn't contain data for the order_id in question, return false.
- * @param string $order_id The order id (transaction id) of the missing payment
- * @param string $log The full path to the log we want to search
- * @return array|boolean The data we sent to the gateway for that order id, or
- * false if we can't find it there.
- */
-function wmf_audit_get_xml_log_data_by_order_id($order_id, $log) {
-  if (!$order_id) {
-    return false;
-  }
-
-  $cmd = 'grep ' . wmfa_execute('get_log_line_grep_string', array('order_id' 
=> $order_id)) . ' ' . $log;
-  wmf_audit_echo(__FUNCTION__ . ' ' . $cmd, true);
-
-  $ret = array();
-  exec(escapeshellcmd($cmd), $ret, $errorlevel);
-
-  if (count($ret) > 0) {
-
-    //In this wonderful new world, we only expect one line.
-    if (count($ret) > 1) {
-      wmf_audit_echo("Odd: More than one logline returned for $order_id. 
Investigation Required.");
-    }
-
-    //just take the last one, just in case somebody did manage to do a 
duplicate.
-    $line = $ret[count($ret) - 1];
-    // $linedata for *everything* from payments goes Month, day, time, box, 
bucket, CTID:OID, absolute madness with lots of unpredictable spaces.
-    $linedata = explode(' ', $line);
-    $contribution_id = explode(':', $linedata[5]);
-    $contribution_id = $contribution_id[0];
-
-
-    //look for the raw xml
-    $full_xml = false;
-    $node = wmfa_execute('get_log_line_xml_outermost_node');
-    $xmlstart = strpos($line, '<?xml');
-    if ($xmlstart === false) {
-      $xmlstart = strpos($line, "<$node>");
-    }
-    $xmlend = strpos($line, "</$node>");
-    if ($xmlend) {
-      $full_xml = true;
-      $xmlend += (strlen($node) + 3);
-      $xml = substr($line, $xmlstart, $xmlend - $xmlstart);
-    } else {
-      //this is a broken line, and it won't load... but we can still parse 
what's left of the thing, the slow way.
-      $xml = substr($line, $xmlstart);
-    }
-    // Syslog wart.  Other control characters should be encoded normally.
-    $xml = str_replace( '#012', "\n", $xml );
-
-    $donor_data = array();
-
-    if ($full_xml) {
-      $xmlobj = new DomDocument;
-      $xmlobj->loadXML($xml);
-
-      $parent_nodes = wmfa_execute('get_log_line_xml_parent_nodes');
-
-      if (empty($parent_nodes)) {
-        wmf_audit_log_error(__FUNCTION__ . ': No parent nodes defined. Can not 
continue.', 'RUNTIME_ERROR');
-        die("Stop dying here");
-      }
-
-      foreach ($parent_nodes as $parent_node) {
-        foreach ($xmlobj->getElementsByTagName($parent_node) as $node) {
-          foreach ($node->childNodes as $childnode) {
-            if (trim($childnode->nodeValue) != '') {
-              $donor_data[$childnode->nodeName] = $childnode->nodeValue;
-            }
-          }
-        }
-      }
-    } else {
-      //the XML got cut off prematurely, probably because syslog was set up to 
truncate on payments.
-      //rebuild what we can the old-fashioned way.
-      $search_for_nodes = wmfa_execute('get_log_line_xml_data_nodes');
-
-      if (empty($search_for_nodes)) {
-        wmf_audit_log_error(__FUNCTION__ . ': No searchable nodes defined. Can 
not continue.', 'RUNTIME_ERROR');
-        die("Stop dying here");
-      }
-      foreach ($search_for_nodes as $node) {
-        $tmp = wmf_audit_get_partial_xml_node_value($node, $xml);
-        if (!is_null($tmp)) {
-          $donor_data[$node] = $tmp;
-        }
-      }
-    }
-
-    if (!empty($donor_data)) {
-      $donor_data['contribution_tracking_id'] = $contribution_id;
-      return $donor_data;
-    } else {
-      wmf_audit_log_error("We found a transaction in the logs for $order_id, 
but there's nothing left after we tried to grab its data. Investigation 
required.", 'DATA_WEIRD');
-    }
-  }
-  return false; //no big deal, it just wasn't there. This will happen most of 
the time.
 }
 
 /**
@@ -1097,7 +280,6 @@
   $value = substr($xml, $valstart, $valend - $valstart);
   return $value;
 }
-
 
 /**
  * Returns the contribution tracking data for $record, if we can find it.
diff --git a/sites/all/modules/wmf_audit/worldpay/WorldpayAuditProcessor.php 
b/sites/all/modules/wmf_audit/worldpay/WorldpayAuditProcessor.php
new file mode 100644
index 0000000..a3f5780
--- /dev/null
+++ b/sites/all/modules/wmf_audit/worldpay/WorldpayAuditProcessor.php
@@ -0,0 +1,490 @@
+<?php
+
+use SmashPig\PaymentProviders\WorldPay\Audit\WorldPayAudit;
+
+class WorldpayAuditProcessor extends BaseAuditProcessor {
+    /**
+     * Returns the configurable path to the recon files
+     * @return string Path to the directory
+     */
+    protected function get_recon_dir() {
+      return variable_get( 'worldpay_audit_recon_files_dir', 
WP_AUDIT_RECON_FILES_DIR );
+    }
+
+    /**
+     * Returns the configurable path to the completed recon files
+     * @return string Path to the directory
+     */
+    protected function get_recon_completed_dir() {
+      return variable_get('worldpay_audit_recon_completed_dir', 
WP_AUDIT_RECON_COMPLETED_DIR);
+    }
+
+    /**
+     * Returns the configurable path to the working log dir
+     * @return string Path to the directory
+     */
+    protected function get_working_log_dir() {
+      return variable_get('worldpay_audit_working_log_dir', 
WP_AUDIT_WORKING_LOG_DIR);
+    }
+
+    /**
+     * Returns the configurable number of days we want to jump back in to the 
past,
+     * to look for transactions in the payments logs.
+     * @return in Number of days
+     */
+    protected function get_log_days_in_past() {
+      return variable_get('worldpay_audit_log_search_past_days', 
WP_AUDIT_LOG_SEARCH_PAST_DAYS);
+    }
+
+    /**
+     * The regex to use to determine if a file is a reconciliation file for 
this
+     * gateway.
+     * @return string regular expression
+     */
+    protected function regex_for_recon() {
+      //WP recon files look like the following thing:
+      //  MA.PISCESSW.#M.RECON.WIKI.D280514
+      // or
+      //  TranDetVer2_530860_11-26-2014_8'27'08 AM.csv
+      return '/\.RECON\.WIKI\.|TranDetVer2/';
+    }
+
+    /**
+     * The regex to use to determine if a file is a working log for this 
gateway.
+     * @return string regular expression
+     */
+    protected function regex_for_working_log() {
+      return '/\d{8}_worldpay\.working/';
+    }
+
+    /**
+     * The regex to use to determine if a file is a compressed payments log.
+     * @return string regular expression
+     */
+    protected function regex_for_compressed_log() {
+      return '/.gz/';
+    }
+
+    /**
+     * The regex to use to determine if a file is an uncompressed log for this
+     * gateway.
+     * @return string regular expression
+     */
+    protected function regex_for_uncompressed_log() {
+      return '/worldpay_\d{8}/';
+    }
+
+    /**
+     * Given a reconciliation file name, pull the date out of the filename and
+     * return it in YYYYMMDD format.
+     * @param string $file Name of the recon file (not full path)
+     * @return string date in YYYYMMDD format
+     */
+    protected function get_recon_file_date( $file ){
+      // FIXME: this is two-fer-one functionality.  Unmash.
+
+      if ( preg_match( '/\.RECON\.WIKI\./', $file ) ) {
+         //WP recon files look like the following thing:
+         //  MA.PISCESSW.#M.RECON.WIKI.D280514
+         //For that, we'd want to return 20140518
+         $parts = explode('.', $file);
+         $end_piece = $parts[count($parts)-1];
+         $date = '20' . substr( $end_piece, 5, 2) . substr( $end_piece, 3, 2) 
. substr( $end_piece, 1, 2);
+         return $date;
+      } elseif ( preg_match( '/TranDetVer2/', $file ) ) {
+         // These files have the equally crazy format:
+         //   TranDetVer2_60879_6-21-2014_8'27'38 AM.csv
+         $parts = explode( '_', $file );
+         $dateStr = $parts[2];
+         $dateParts = explode( '-', $dateStr );
+         $date = $dateParts[2] . str_pad( $dateParts[0], 2, '0' ) . str_pad( 
$dateParts[1], 2, '0' );
+         return $date;
+      }
+      throw new Exception( "Cannot parse date in surprise file {$file}" );
+    }
+
+    /**
+     * Given the name of a working log file, pull out the date portion.
+     * @param string $file Name of the working log file (not full path)
+     * @return string date in YYYYMMDD format
+     */
+    protected function get_working_log_file_date($file) {
+      //  '/\d{8}_worldpay\.working/';
+      $parts = explode('_', $file);
+      return $parts[0];
+    }
+
+    /**
+     * Get the name of a compressed log file based on the supplied date.
+     * @param string $date date in YYYYMMDD format
+     * @return string Name of the file we're looking for
+     */
+    protected function get_compressed_log_file_name($date) {
+    //  payments-worldpay-20140413.gz
+      return "payments-worldpay-$date.gz";
+    }
+
+    /**
+     * Get the name of an uncompressed log file based on the supplied date.
+     * @param string $date date in YYYYMMDD format
+     * @return string Name of the file we're looking for
+     */
+    protected function get_uncompressed_log_file_name($date) {
+    //  payments-worldpay-20140413 - no extension. Weird.
+      return "payments-worldpay-$date";
+    }
+
+    /**
+     * Get the name of a working log file based on the supplied date.
+     * @param string $date date in YYYYMMDD format
+     * @return string Name of the file we're looking for
+     */
+    protected function get_working_log_file_name($date) {
+      //  '/\d{8}_worldpay\.working/';
+      return $date . '_worldpay.working';
+    }
+
+    /**
+     * In order to create the distilled "working" log files, we need to grep 
through
+     * the uncompressed log file for lines that contain a copy of our original
+     * communication with the payment provider. This function returns the 
string
+     * that, when used in a command-line grep statement against a payments 
log, will
+     * pick out all instances of communication with the 3rd party that contain 
donor
+     * information that is not included in the recon file, but which we need to
+     * re-fuse with the recon data and save in civicrm.
+     * @return string the pattern we're going to grep for in the payments logs
+     */
+    protected function get_log_distilling_grep_string() {
+      return 'Request XML.*TMSTN.*<TransactionType>PT.*<RequestType>S<';
+    }
+
+    /**
+     * Just parse one recon file.
+     * @staticvar null $parserClass The class of the parser we're going to use
+     * @param string $file Absolute location of the recon file you want to 
parse
+     * @return mixed An array of recon data, or false
+     */
+    protected function parse_recon_file($file) {
+      $recon_data = array();
+      $recon_parser = new WorldPayAudit();
+
+      $data = null;
+      try {
+       $data = $recon_parser->parseFile($file);
+      } catch (Exception $e) {
+       wmf_audit_log_error("Something went amiss with the recon parser while 
processing $file ", 'RECON_PARSE_ERROR');
+      }
+
+      //At this point, $data already contains the usable portions of the file.
+
+      if (!empty($data)) {
+       foreach ($data as $record) {
+         wmf_audit_echo(wmf_audit_echochar($record));
+       }
+      }
+      if ( count( $data ) ){
+                   return $data;
+           }
+           return false;
+    }
+
+    /**
+     * Checks to see if the transaction already exists in civi
+     * @param array $transaction Array of donation data
+     * @return boolean true if it's in there, otherwise false
+     */
+    protected function main_transaction_exists_in_civi($transaction) {
+      //go through the transactions and check to see if they're in civi
+      if (wmf_civicrm_get_contributions_from_gateway_id('worldpay', 
$transaction['gateway_txn_id']) === false) {
+       return false;
+      } else {
+       return true;
+      }
+    }
+
+    /**
+     * Checks to see if the refund or chargeback already exists in civi.
+     * NOTE: This does not check to see if the parent is present at all, nor 
should
+     * it. Call worldpay_audit_main_transaction_exists_in_civi for that.
+     * @param array $transaction Array of donation data
+     * @return boolean true if it's in there, otherwise false
+     */
+    protected function negative_transaction_exists_in_civi($transaction) {
+      //go through the transactions and check to see if they're in civi
+      if (wmf_civicrm_get_child_contributions_from_gateway_id('worldpay', 
$transaction['gateway_txn_id']) === false) {
+       return false;
+      } else {
+       return true;
+      }
+    }
+
+    /**
+     * Checks the array to see if the data inside is describing a refund.
+     * @param aray $record The transaction we would like to know is a refund 
or not.
+     * @return boolean true if it is, otherwise false
+     */
+    protected function record_is_refund($record) {
+      if (array_key_exists('type', $record) && $record['type'] === 'refund') {
+       return true;
+      }
+      return false;
+    }
+
+    /**
+     * Checks the array to see if the data inside is describing a chargeback.
+     * @param aray $record The transaction we would like to know is a 
chargeback or
+     * not.
+     * @return boolean true if it is, otherwise false
+     */
+    protected function record_is_chargeback($record) {
+      //@TODO: there should be more here. But we have no examples yet.
+      return false;
+    }
+
+    /**
+     * Return a date in the format YYYYMMDD for the given record
+     * @param array $record A transaction, or partial transaction
+     * @return string Date in YYYYMMDD format
+     */
+    protected function get_record_human_date($record) {
+      if (array_key_exists('date', $record)) {
+       return date(WMF_DATEFORMAT, $record['date']); //date format defined in 
wmf_dates
+      }
+
+      echo print_r($record, true);
+      die(__FUNCTION__ . ': No date present in the record. This seems like a 
problem.');
+    }
+
+    /**
+     * In order to match data in working payments logs with the data we're 
trying to
+     * rebuild, we will need to grep for something. This, actually.
+     * @param string $order_id The order id (transaction id) of what we're 
looking for.
+     * @return string the pattern to grep for, for this specific transaction
+     */
+    protected function get_log_line_grep_string($order_id) {
+      return "<OrderNumber>$order_id</OrderNumber>";
+    }
+
+    /**
+     * The name of the outermost node in the log XML we're going to try to make
+     * sense of. Usually, this is "XML", but apparently not all the time. Huh.
+     * @return string node name
+     */
+    protected function get_log_line_xml_outermost_node() {
+      return 'TMSTN';
+    }
+
+    /**
+     * This would probably make more sense if this was not the same as the
+     * outermost. WP is really... flat.
+     * *ahem*
+     * If the XML is complete (not truncated by syslog), then the data just 
comes
+     * straight from the child nodes of the parent nodes defined here.
+     * @return array Names of the nodes whose children describe donor data.
+     */
+    protected function get_log_line_xml_parent_nodes() {
+      return array(
+       'TMSTN'
+      );
+    }
+
+    /**
+     * If the XML happens to be incomplete (truncated by syslog), we avoid 
total
+     * tragedy by searching whatever is left for individual complete data 
nodes.
+     * Yes: This has happened before.
+     * @return array Names of the nodes whose children describe donor data.
+     */
+    protected function get_log_line_xml_data_nodes() {
+      return array(
+       'OrderNumber',
+       'Amount',
+       'REMOTE_ADDR',
+       'FirstName',
+       'LastName',
+       'Address1',
+       'ZipCode',
+       'CountryCode',
+       'Email',
+      );
+    }
+
+    /**
+     * Grabs just the order_id out of a $transaction. This makes more sense if 
the
+     * parser doesn't normalize as well as the WP one does, or even at all.
+     * @param array $transaction possibly incomplete set of transaction data
+     * @return string|false the order_id, or false if we can't figure it out
+     */
+    protected function get_order_id($transaction) {
+      if (is_array($transaction) && array_key_exists('gateway_txn_id', 
$transaction)) {
+       return $transaction['gateway_txn_id'];
+      }
+      return false;
+    }
+
+    /**
+     * Normalize refund/chargeback messages before sending
+     * @param array $record transaction data
+     * @return array The normalized data we want to send
+     */
+    protected function normalize_negative($record) {
+      $send_message = array(
+       'gateway_refund_id' => 'RFD' . $record['gateway_txn_id'], //Notes from 
a previous version: "after intense deliberation, we don't actually care what 
this is at all."
+       'gateway_parent_id' => $record['gateway_txn_id'], //gateway transaction 
ID
+       'gross_currency' => $record['currency'], //currency code
+       'gross' => $record['gross'], //amount
+       'date' => $record['date'], //timestamp
+       'gateway' => 'worldpay', //lcase
+    //  'gateway_account' => $record['gateway_account'], //BOO. @TODO: Later.
+    //  'payment_method' => $record['payment_method'], //Argh. Not telling you.
+    //  'payment_submethod' => $record['payment_submethod'], //Still not 
telling you.
+       'type' => $record['type'], //This actually works here. Weird, right?
+      );
+      return $send_message;
+    }
+
+    /**
+     * Used in makemissing mode
+     * This should take care of any extra data not sent in the recon file, 
that will
+     * actually make qc choke. Not so necessary with WP, but this will need to
+     * happen elsewhere, probably. Just thinking ahead.
+     * @param array $record transaction data
+     * @return type The normalized data we want to send.
+     */
+    protected function normalize_partial($record) {
+      //@TODO: Still need gateway account to go in here when that happens.
+      return $record;
+    }
+
+    /**
+     * Takes whatever we've found in the xml, and whatever we've found in the 
recon
+     * file, and makes them get along. If that works, it fuses them into a 
normal
+     * message.
+     * Presumably, if we're calling this function, we already have reason to 
believe
+     * that the xml and recon data supplied go together. There should always 
be some
+     * internal sanity checking, though. Stuff can get weird.
+     * @param array $xml_data Transaction data from payments log xml
+     * @param array $recon_data Transaction data from the recon file
+     * @return array|false The re-fused and normalized data, or false if 
something
+     * went wrong.
+     */
+    protected function normalize_and_merge_data($xml_data, $recon_data) {
+      if (empty($xml_data) || empty($recon_data)) {
+       $message = ": Missing one of the required arrays.\nXML Data: " . 
print_r($xml_data, true) . "\nRecon Data: " . print_r($recon_data, true);
+       wmf_audit_log_error(__FUNCTION__ . $message, 'DATA_WEIRD');
+       return false;
+      }
+      $normal = array();
+
+      //first, normalize the $xml_data
+      $nodemap = array(
+       'OrderNumber' => 'gateway_txn_id',
+       'Amount' => 'gross',
+       'REMOTE_ADDR' => 'user_ip',
+       'FirstName' => 'first_name',
+       'LastName' => 'last_name',
+       'Address1' => 'street_address', //N0NE PROVIDED
+       'ZipCode' => 'postal_code', //0
+       'CountryCode' => 'country',
+       'Email' => 'email',
+       'contribution_tracking_id' => 'contribution_tracking_id', //passed 
through, because I already set this manually
+      );
+
+      foreach ($xml_data as $key => $value) {
+       if (array_key_exists($key, $nodemap)) {
+         $normal[$nodemap[$key]] = $value;
+       }
+      }
+
+      if (array_key_exists('CurrencyId', $xml_data)) {
+       $normal['currency'] = 
worldpay_audit_get_currency_code_from_stupid_number($xml_data['CurrencyId']);
+      }
+
+      $unsets = array(
+       'street_address' => 'N0NE PROVIDED',
+       'postal_code' => '0',
+      );
+
+      foreach ($unsets as $key => $value) {
+       if (array_key_exists($key, $normal) && $normal[$key] === $value) {
+         unset($normal[$key]);
+       }
+      }
+
+      //now, cross-reference what's in $recon_data and complain loudly if 
something doesn't match.
+      //@TODO: see if there's a way we can usefully use [settlement_currency] 
and [settlement_amount]
+      //from the recon file. This is actually super useful, but might require 
new import code and/or schema change.
+      //
+      //Check between the two sets... normal => recon
+      $cross_check = array(
+       'currency' => 'currency',
+       'gross' => 'gross',
+      );
+
+      foreach ($cross_check as $check1 => $check2) {
+       if (array_key_exists($check1, $normal) && array_key_exists($check2, 
$recon_data)) {
+         if (is_numeric($normal[$check1])) {
+           //I actually hate everything.
+           //Floatval all by itself doesn't do the job, even if I turn the !== 
into !=.
+           //"Data mismatch between normal gross (5) and recon gross (5)."
+           $normal[$check1] = (string) floatval($normal[$check1]);
+           $recon_data[$check2] = (string) floatval($recon_data[$check2]);
+         }
+         if ($normal[$check1] !== $recon_data[$check2]) {
+           wmf_audit_log_error("Data mismatch between normal $check1 
($normal[$check1]) and recon $check2 ($recon_data[$check2]). Investigation 
required. " . print_r($recon_data, true), 'DATA_INCONSISTENT');
+           return false;
+         }
+       } else {
+         wmf_audit_log_error("Recon data is expecting normal $check1 and recon 
$check2, but at least one is missing. Investigation required. " . 
print_r($recon_data, true), 'DATA_INCONSISTENT');
+         return false;
+       }
+      }
+
+      //just port these
+      $believe = array(//because we have no choice
+       'gateway', //ha
+       'date',
+       'payment_method',
+       'payment_submethod',
+       'gross',
+      );
+
+      foreach ($believe as $key) {
+       if (array_key_exists($key, $recon_data)) {
+         $normal[$key] = $recon_data[$key];
+       } else {
+         wmf_audit_log_error("Recon data is missing expected key $key. " . 
print_r($recon_data, true), 'DATA_INCOMPLETE');
+       }
+      }
+
+      return $normal;
+    }
+
+    /**
+     * HELPER FUNCTIONS
+     * ...only this file will ever call them.
+     */
+
+    /**
+     * Instead of using sane ISO codes for currencies, WP uses a long list of
+     * numbers that everybody immediately turns back into the ISO codes at the
+     * earliest convenience.
+     * This array is backwards, because I straight up copied it from
+     * DonationInterface. Not even sorry.
+     * @staticvar array $flipped The currency code array in the format we need 
to do
+     * an easy lookup.
+     * @param int $number The number that appears in WP payments log XML for 
the
+     * currency code.
+     * @return string The ISO currency code
+     */
+    protected function get_currency_code_from_stupid_number($number) {
+      static $flipped = array();
+      if (empty($flipped)) {
+       $flipped = array_flip(WorldPayAdapter::$CURRENCY_CODES);
+      }
+      if (array_key_exists($number, $flipped)) {
+       return $flipped[$number];
+      } else {
+       wmf_audit_log_error(__FUNCTION__ . ": No currency found for code 
$number", 'MISSING_MANDATORY_DATA');
+      }
+    }
+}
diff --git a/sites/all/modules/wmf_audit/worldpay/worldpay_audit.info 
b/sites/all/modules/wmf_audit/worldpay/worldpay_audit.info
index e989061..430d3e3 100644
--- a/sites/all/modules/wmf_audit/worldpay/worldpay_audit.info
+++ b/sites/all/modules/wmf_audit/worldpay/worldpay_audit.info
@@ -1,6 +1,7 @@
-name = WorldPay Audit
-description = Auditing framework for contacts and contributions that come in 
through nightly reconciliation files, for WorldPay
+name = Worldpay Audit
+description = Auditing framework for contacts and contributions that come in 
through nightly reconciliation files, for Worldpay
 core = 7.x
 dependencies[] = wmf_audit
 package = WMF Audit
 configure = admin/config/wmf_audit/worldpay_audit
+files[] = WorldpayAuditProcessor.php
diff --git a/sites/all/modules/wmf_audit/worldpay/worldpay_audit.module 
b/sites/all/modules/wmf_audit/worldpay/worldpay_audit.module
index 8854a1c..b83e6ef 100644
--- a/sites/all/modules/wmf_audit/worldpay/worldpay_audit.module
+++ b/sites/all/modules/wmf_audit/worldpay/worldpay_audit.module
@@ -7,8 +7,6 @@
 define('WP_AUDIT_LOG_SEARCH_PAST_DAYS', 7);
 define( 'WP_AUDIT_TEST_MODE', true );
 
-use SmashPig\PaymentProviders\WorldPay\Audit\WorldPayAudit;
-
 /**
  * Implementation of hook_menu()
  */
@@ -62,489 +60,3 @@
   );
   return system_settings_form( $form );
 }
-
-/**
- * Returns the configurable path to the recon files
- * @return string Path to the directory
- */
-function worldpay_audit_get_recon_dir() {
-  return variable_get( 'worldpay_audit_recon_files_dir', 
WP_AUDIT_RECON_FILES_DIR );
-}
-
-/**
- * Returns the configurable path to the completed recon files
- * @return string Path to the directory
- */
-function worldpay_audit_get_recon_completed_dir() {
-  return variable_get('worldpay_audit_recon_completed_dir', 
WP_AUDIT_RECON_COMPLETED_DIR);
-}
-
-/**
- * Returns the configurable path to the working log dir
- * @return string Path to the directory
- */
-function worldpay_audit_get_working_log_dir() {
-  return variable_get('worldpay_audit_working_log_dir', 
WP_AUDIT_WORKING_LOG_DIR);
-}
-
-/**
- * Returns the configurable number of days we want to jump back in to the past,
- * to look for transactions in the payments logs.
- * @return in Number of days
- */
-function worldpay_audit_get_log_days_in_past() {
-  return variable_get('worldpay_audit_log_search_past_days', 
WP_AUDIT_LOG_SEARCH_PAST_DAYS);
-}
-
-/**
- * The regex to use to determine if a file is a reconciliation file for this
- * gateway.
- * @return string regular expression
- */
-function worldpay_audit_regex_for_recon() {
-  //WP recon files look like the following thing:
-  //  MA.PISCESSW.#M.RECON.WIKI.D280514
-  // or
-  //  TranDetVer2_530860_11-26-2014_8'27'08 AM.csv
-  return '/\.RECON\.WIKI\.|TranDetVer2/';
-}
-
-/**
- * The regex to use to determine if a file is a working log for this gateway.
- * @return string regular expression
- */
-function worldpay_audit_regex_for_working_log() {
-  return '/\d{8}_worldpay\.working/';
-}
-
-/**
- * The regex to use to determine if a file is a compressed payments log.
- * @return string regular expression
- */
-function worldpay_audit_regex_for_compressed_log() {
-  return '/.gz/';
-}
-
-/**
- * The regex to use to determine if a file is an uncompressed log for this
- * gateway.
- * @return string regular expression
- */
-function worldpay_audit_regex_for_uncompressed_log() {
-  return '/worldpay_\d{8}/';
-}
-
-/**
- * Given a reconciliation file name, pull the date out of the filename and
- * return it in YYYYMMDD format.
- * @param string $file Name of the recon file (not full path)
- * @return string date in YYYYMMDD format
- */
-function worldpay_audit_get_recon_file_date( $file ){
-  // FIXME: this is two-fer-one functionality.  Unmash.
-
-  if ( preg_match( '/\.RECON\.WIKI\./', $file ) ) {
-      //WP recon files look like the following thing:
-      //  MA.PISCESSW.#M.RECON.WIKI.D280514
-      //For that, we'd want to return 20140518
-      $parts = explode('.', $file);
-      $end_piece = $parts[count($parts)-1];
-      $date = '20' . substr( $end_piece, 5, 2) . substr( $end_piece, 3, 2) . 
substr( $end_piece, 1, 2);
-      return $date;
-  } elseif ( preg_match( '/TranDetVer2/', $file ) ) {
-      // These files have the equally crazy format:
-      //   TranDetVer2_60879_6-21-2014_8'27'38 AM.csv
-      $parts = explode( '_', $file );
-      $dateStr = $parts[2];
-      $dateParts = explode( '-', $dateStr );
-      $date = $dateParts[2] . str_pad( $dateParts[0], 2, '0' ) . str_pad( 
$dateParts[1], 2, '0' );
-      return $date;
-  }
-  throw new Exception( "Cannot parse date in surprise file {$file}" );
-}
-
-/**
- * Given the name of a working log file, pull out the date portion.
- * @param string $file Name of the working log file (not full path)
- * @return string date in YYYYMMDD format
- */
-function worldpay_audit_get_working_log_file_date($file) {
-  //  '/\d{8}_worldpay\.working/';
-  $parts = explode('_', $file);
-  return $parts[0];
-}
-
-/**
- * Get the name of a compressed log file based on the supplied date.
- * @param string $date date in YYYYMMDD format
- * @return string Name of the file we're looking for
- */
-function worldpay_audit_get_compressed_log_file_name($date) {
-//  payments-worldpay-20140413.gz
-  return "payments-worldpay-$date.gz";
-}
-
-/**
- * Get the name of an uncompressed log file based on the supplied date.
- * @param string $date date in YYYYMMDD format
- * @return string Name of the file we're looking for
- */
-function worldpay_audit_get_uncompressed_log_file_name($date) {
-//  payments-worldpay-20140413 - no extension. Weird.
-  return "payments-worldpay-$date";
-}
-
-/**
- * Get the name of a working log file based on the supplied date.
- * @param string $date date in YYYYMMDD format
- * @return string Name of the file we're looking for
- */
-function worldpay_audit_get_working_log_file_name($date) {
-  //  '/\d{8}_worldpay\.working/';
-  return $date . '_worldpay.working';
-}
-
-/**
- * In order to create the distilled "working" log files, we need to grep 
through
- * the uncompressed log file for lines that contain a copy of our original
- * communication with the payment provider. This function returns the string
- * that, when used in a command-line grep statement against a payments log, 
will
- * pick out all instances of communication with the 3rd party that contain 
donor
- * information that is not included in the recon file, but which we need to
- * re-fuse with the recon data and save in civicrm.
- * @return string the pattern we're going to grep for in the payments logs
- */
-function worldpay_audit_get_log_distilling_grep_string() {
-  return 'Request XML.*TMSTN.*<TransactionType>PT.*<RequestType>S<';
-}
-
-/**
- * Just parse one recon file.
- * @staticvar null $parserClass The class of the parser we're going to use
- * @param string $file Absolute location of the recon file you want to parse
- * @return mixed An array of recon data, or false
- */
-function worldpay_audit_parse_recon_file($file) {
-  $recon_data = array();
-  $recon_parser = new WorldPayAudit();
-
-  $data = null;
-  try {
-    $data = $recon_parser->parseFile($file);
-  } catch (Exception $e) {
-    wmf_audit_log_error("Something went amiss with the recon parser while 
processing $file ", 'RECON_PARSE_ERROR');
-  }
-
-  //At this point, $data already contains the usable portions of the file.
-
-  if (!empty($data)) {
-    foreach ($data as $record) {
-      wmf_audit_echo(wmf_audit_echochar($record));
-    }
-  }
-  if ( count( $data ) ){
-               return $data;
-       }
-       return false;
-}
-
-/**
- * Checks to see if the transaction already exists in civi
- * @param array $transaction Array of donation data
- * @return boolean true if it's in there, otherwise false
- */
-function worldpay_audit_main_transaction_exists_in_civi($transaction) {
-  //go through the transactions and check to see if they're in civi
-  if (wmf_civicrm_get_contributions_from_gateway_id('worldpay', 
$transaction['gateway_txn_id']) === false) {
-    return false;
-  } else {
-    return true;
-  }
-}
-
-/**
- * Checks to see if the refund or chargeback already exists in civi.
- * NOTE: This does not check to see if the parent is present at all, nor should
- * it. Call worldpay_audit_main_transaction_exists_in_civi for that.
- * @param array $transaction Array of donation data
- * @return boolean true if it's in there, otherwise false
- */
-function worldpay_audit_negative_transaction_exists_in_civi($transaction) {
-  //go through the transactions and check to see if they're in civi
-  if (wmf_civicrm_get_child_contributions_from_gateway_id('worldpay', 
$transaction['gateway_txn_id']) === false) {
-    return false;
-  } else {
-    return true;
-  }
-}
-
-/**
- * Checks the array to see if the data inside is describing a refund.
- * @param aray $record The transaction we would like to know is a refund or 
not.
- * @return boolean true if it is, otherwise false
- */
-function worldpay_audit_record_is_refund($record) {
-  if (array_key_exists('type', $record) && $record['type'] === 'refund') {
-    return true;
-  }
-  return false;
-}
-
-/**
- * Checks the array to see if the data inside is describing a chargeback.
- * @param aray $record The transaction we would like to know is a chargeback or
- * not.
- * @return boolean true if it is, otherwise false
- */
-function worldpay_audit_record_is_chargeback($record) {
-  //@TODO: there should be more here. But we have no examples yet.
-  return false;
-}
-
-/**
- * Return a date in the format YYYYMMDD for the given record
- * @param array $record A transaction, or partial transaction
- * @return string Date in YYYYMMDD format
- */
-function worldpay_audit_get_record_human_date($record) {
-  if (array_key_exists('date', $record)) {
-    return date(WMF_DATEFORMAT, $record['date']); //date format defined in 
wmf_dates
-  }
-
-  echo print_r($record, true);
-  die(__FUNCTION__ . ': No date present in the record. This seems like a 
problem.');
-}
-
-/**
- * In order to match data in working payments logs with the data we're trying 
to
- * rebuild, we will need to grep for something. This, actually.
- * @param string $order_id The order id (transaction id) of what we're looking 
for.
- * @return string the pattern to grep for, for this specific transaction
- */
-function worldpay_audit_get_log_line_grep_string($order_id) {
-  return "<OrderNumber>$order_id</OrderNumber>";
-}
-
-/**
- * The name of the outermost node in the log XML we're going to try to make
- * sense of. Usually, this is "XML", but apparently not all the time. Huh.
- * @return string node name
- */
-function worldpay_audit_get_log_line_xml_outermost_node() {
-  return 'TMSTN';
-}
-
-/**
- * This would probably make more sense if this was not the same as the
- * outermost. WP is really... flat.
- * *ahem*
- * If the XML is complete (not truncated by syslog), then the data just comes
- * straight from the child nodes of the parent nodes defined here.
- * @return array Names of the nodes whose children describe donor data.
- */
-function worldpay_audit_get_log_line_xml_parent_nodes() {
-  return array(
-    'TMSTN'
-  );
-}
-
-/**
- * If the XML happens to be incomplete (truncated by syslog), we avoid total
- * tragedy by searching whatever is left for individual complete data nodes.
- * Yes: This has happened before.
- * @return array Names of the nodes whose children describe donor data.
- */
-function worldpay_audit_get_log_line_xml_data_nodes() {
-  return array(
-    'OrderNumber',
-    'Amount',
-    'REMOTE_ADDR',
-    'FirstName',
-    'LastName',
-    'Address1',
-    'ZipCode',
-    'CountryCode',
-    'Email',
-  );
-}
-
-/**
- * Grabs just the order_id out of a $transaction. This makes more sense if the
- * parser doesn't normalize as well as the WP one does, or even at all.
- * @param array $transaction possibly incomplete set of transaction data
- * @return string|false the order_id, or false if we can't figure it out
- */
-function worldpay_audit_get_order_id($transaction) {
-  if (is_array($transaction) && array_key_exists('gateway_txn_id', 
$transaction)) {
-    return $transaction['gateway_txn_id'];
-  }
-  return false;
-}
-
-/**
- * Normalize refund/chargeback messages before sending
- * @param array $record transaction data
- * @return array The normalized data we want to send
- */
-function worldpay_audit_normalize_negative($record) {
-  $send_message = array(
-    'gateway_refund_id' => 'RFD' . $record['gateway_txn_id'], //Notes from a 
previous version: "after intense deliberation, we don't actually care what this 
is at all."
-    'gateway_parent_id' => $record['gateway_txn_id'], //gateway transaction ID
-    'gross_currency' => $record['currency'], //currency code
-    'gross' => $record['gross'], //amount
-    'date' => $record['date'], //timestamp
-    'gateway' => 'worldpay', //lcase
-//  'gateway_account' => $record['gateway_account'], //BOO. @TODO: Later.
-//  'payment_method' => $record['payment_method'], //Argh. Not telling you.
-//  'payment_submethod' => $record['payment_submethod'], //Still not telling 
you.
-    'type' => $record['type'], //This actually works here. Weird, right?
-  );
-  return $send_message;
-}
-
-/**
- * Used in makemissing mode
- * This should take care of any extra data not sent in the recon file, that 
will
- * actually make qc choke. Not so necessary with WP, but this will need to
- * happen elsewhere, probably. Just thinking ahead.
- * @param array $record transaction data
- * @return type The normalized data we want to send.
- */
-function worldpay_audit_normalize_partial($record) {
-  //@TODO: Still need gateway account to go in here when that happens.
-  return $record;
-}
-
-/**
- * Takes whatever we've found in the xml, and whatever we've found in the recon
- * file, and makes them get along. If that works, it fuses them into a normal
- * message.
- * Presumably, if we're calling this function, we already have reason to 
believe
- * that the xml and recon data supplied go together. There should always be 
some
- * internal sanity checking, though. Stuff can get weird.
- * @param array $xml_data Transaction data from payments log xml
- * @param array $recon_data Transaction data from the recon file
- * @return array|false The re-fused and normalized data, or false if something
- * went wrong.
- */
-function worldpay_audit_normalize_and_merge_data($xml_data, $recon_data) {
-  if (empty($xml_data) || empty($recon_data)) {
-    $message = ": Missing one of the required arrays.\nXML Data: " . 
print_r($xml_data, true) . "\nRecon Data: " . print_r($recon_data, true);
-    wmf_audit_log_error(__FUNCTION__ . $message, 'DATA_WEIRD');
-    return false;
-  }
-  $normal = array();
-
-  //first, normalize the $xml_data
-  $nodemap = array(
-    'OrderNumber' => 'gateway_txn_id',
-    'Amount' => 'gross',
-    'REMOTE_ADDR' => 'user_ip',
-    'FirstName' => 'first_name',
-    'LastName' => 'last_name',
-    'Address1' => 'street_address', //N0NE PROVIDED
-    'ZipCode' => 'postal_code', //0
-    'CountryCode' => 'country',
-    'Email' => 'email',
-    'contribution_tracking_id' => 'contribution_tracking_id', //passed 
through, because I already set this manually
-  );
-
-  foreach ($xml_data as $key => $value) {
-    if (array_key_exists($key, $nodemap)) {
-      $normal[$nodemap[$key]] = $value;
-    }
-  }
-
-  if (array_key_exists('CurrencyId', $xml_data)) {
-    $normal['currency'] = 
worldpay_audit_get_currency_code_from_stupid_number($xml_data['CurrencyId']);
-  }
-
-  $unsets = array(
-    'street_address' => 'N0NE PROVIDED',
-    'postal_code' => '0',
-  );
-
-  foreach ($unsets as $key => $value) {
-    if (array_key_exists($key, $normal) && $normal[$key] === $value) {
-      unset($normal[$key]);
-    }
-  }
-
-  //now, cross-reference what's in $recon_data and complain loudly if 
something doesn't match.
-  //@TODO: see if there's a way we can usefully use [settlement_currency] and 
[settlement_amount]
-  //from the recon file. This is actually super useful, but might require new 
import code and/or schema change.
-  //
-  //Check between the two sets... normal => recon
-  $cross_check = array(
-    'currency' => 'currency',
-    'gross' => 'gross',
-  );
-
-  foreach ($cross_check as $check1 => $check2) {
-    if (array_key_exists($check1, $normal) && array_key_exists($check2, 
$recon_data)) {
-      if (is_numeric($normal[$check1])) {
-        //I actually hate everything.
-        //Floatval all by itself doesn't do the job, even if I turn the !== 
into !=.
-        //"Data mismatch between normal gross (5) and recon gross (5)."
-        $normal[$check1] = (string) floatval($normal[$check1]);
-        $recon_data[$check2] = (string) floatval($recon_data[$check2]);
-      }
-      if ($normal[$check1] !== $recon_data[$check2]) {
-        wmf_audit_log_error("Data mismatch between normal $check1 
($normal[$check1]) and recon $check2 ($recon_data[$check2]). Investigation 
required. " . print_r($recon_data, true), 'DATA_INCONSISTENT');
-        return false;
-      }
-    } else {
-      wmf_audit_log_error("Recon data is expecting normal $check1 and recon 
$check2, but at least one is missing. Investigation required. " . 
print_r($recon_data, true), 'DATA_INCONSISTENT');
-      return false;
-    }
-  }
-
-  //just port these
-  $believe = array(//because we have no choice
-    'gateway', //ha
-    'date',
-    'payment_method',
-    'payment_submethod',
-    'gross',
-  );
-
-  foreach ($believe as $key) {
-    if (array_key_exists($key, $recon_data)) {
-      $normal[$key] = $recon_data[$key];
-    } else {
-      wmf_audit_log_error("Recon data is missing expected key $key. " . 
print_r($recon_data, true), 'DATA_INCOMPLETE');
-    }
-  }
-
-  return $normal;
-}
-
-/**
- * HELPER FUNCTIONS
- * ...only this file will ever call them.
- */
-
-/**
- * Instead of using sane ISO codes for currencies, WP uses a long list of
- * numbers that everybody immediately turns back into the ISO codes at the
- * earliest convenience.
- * This array is backwards, because I straight up copied it from
- * DonationInterface. Not even sorry.
- * @staticvar array $flipped The currency code array in the format we need to 
do
- * an easy lookup.
- * @param int $number The number that appears in WP payments log XML for the
- * currency code.
- * @return string The ISO currency code
- */
-function worldpay_audit_get_currency_code_from_stupid_number($number) {
-  static $flipped = array();
-  if (empty($flipped)) {
-    $flipped = array_flip(WorldPayAdapter::$CURRENCY_CODES);
-  }
-  if (array_key_exists($number, $flipped)) {
-    return $flipped[$number];
-  } else {
-    wmf_audit_log_error(__FUNCTION__ . ": No currency found for code $number", 
'MISSING_MANDATORY_DATA');
-  }
-}
-

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

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

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

Reply via email to