fix paymentech batch approval status processing, RT#80622
[freeside.git] / FS / FS / pay_batch / paymentech.pm
index 2ac5a66..094d501 100644 (file)
@@ -8,9 +8,10 @@ use Date::Format 'time2str';
 use Date::Parse 'str2time';
 use Tie::IxHash;
 use FS::Conf;
+use Unicode::Truncate 'truncate_egc';
 
 my $conf;
-my ($bin, $merchantID, $terminalID, $username);
+my ($bin, $merchantID, $terminalID, $username, $password, $with_recurringInd);
 $name = 'paymentech';
 
 my $gateway;
@@ -23,7 +24,10 @@ my $gateway;
     '_date',
     'approvalStatus',
     'order_number',
-    'authorization',
+    'auth',
+    'procStatus',
+    'procStatusMessage',
+    'respCodeMessage',
     ],
   xmlkeys     => [
     'orderID',
@@ -31,6 +35,9 @@ my $gateway;
     'approvalStatus',
     'txRefNum',
     'authorizationCode',
+    'procStatus',
+    'procStatusMessage',
+    'respCodeMessage',
     ],
   'hook'        => sub {
       if ( !$gateway ) {
@@ -38,7 +45,7 @@ my $gateway;
         # as the batch config, if there is one.  If not, leave 
         # gateway out entirely.
         my $merchant = (FS::Conf->new->config('batchconfig-paymentech'))[2];
-        my $g = qsearchs({
+        $gateway = qsearchs({
               'table'     => 'payment_gateway',
               'addl_from' => ' JOIN payment_gateway_option USING (gatewaynum) ',
               'hashref'   => {  disabled    => '',
@@ -46,25 +53,22 @@ my $gateway;
                                 optionvalue => $merchant,
                               },
               });
-        $gateway = ($g ? $g->gatewaynum . '-' : '') . 'PaymenTech';
       }
       my ($hash, $oldhash) = @_;
+      $hash->{'gatewaynum'} = $gateway->gatewaynum if $gateway;
+      $hash->{'processor'} = 'PaymenTech';
       my ($mon, $day, $year, $hour, $min, $sec) = 
         $hash->{'_date'} =~ /^(..)(..)(....)(..)(..)(..)$/;
       $hash->{'_date'} = timelocal($sec, $min, $hour, $day, $mon-1, $year);
       $hash->{'paid'} = $oldhash->{'amount'};
-      $hash->{'paybatch'} = join(':', 
-        $gateway,
-        $hash->{'authorization'},
-        $hash->{'order_number'},
-      );
-    },
-  'approved'    => sub { my $hash = shift;
-                            $hash->{'approvalStatus'} 
-    },
-  'declined'    => sub { my $hash = shift;
-                            ! $hash->{'approvalStatus'} 
+      if ( $hash->{'procStatus'} == 0 ) {
+        $hash->{'error_message'} = $hash->{'respCodeMessage'};
+      } else {
+        $hash->{'error_message'} = $hash->{'procStatusMessage'};
+      }
     },
+  'approved'    => sub { shift->{'approvalStatus'} == 1 },
+  'declined'    => sub { shift->{'approvalStatus'} != 1 },
 );
 
 my %paytype = (
@@ -72,7 +76,9 @@ my %paytype = (
   'personal savings'  => 'S',
   'business checking' => 'X',
   'business savings'  => 'X',
-  );
+);
+
+my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
 
 %export_info = (
   init  => sub {
@@ -80,7 +86,7 @@ my %paytype = (
     eval "use XML::Writer";
     die $@ if $@;
     my $conf = shift;
-    ($bin, $terminalID, $merchantID, $username) =
+    ($bin, $terminalID, $merchantID, $username, $password, $with_recurringInd) =
        $conf->config('batchconfig-paymentech');
     },
 # Here we do all the work in the header function.
@@ -89,7 +95,13 @@ my %paytype = (
     my @cust_pay_batch = @{(shift)};
     my $count = 1;
     my $output;
-    my $xml = new XML::Writer(OUTPUT => \$output, DATA_MODE => 1, DATA_INDENT => 2);
+    my $xml = XML::Writer->new(
+      OUTPUT => \$output,
+      DATA_MODE => 1,
+      DATA_INDENT => 2,
+      ENCODING => 'utf-8'
+    );
+    $xml->xmlDecl(); # it is in the spec
     $xml->startTag('transRequest', RequestCount => scalar(@cust_pay_batch) + 1);
     $xml->startTag('batchFileID');
     $xml->dataElement(userID => $username);
@@ -99,31 +111,44 @@ my %paytype = (
 
     foreach (@cust_pay_batch) {
       $xml->startTag('newOrder', BatchRequestNo => $count++);
+      my $status = $_->cust_main->status;
       tie my %order, 'Tie::IxHash', (
-        industryType => 'EC',
-        transType    => 'AC',
-        bin          => $bin,
-        merchantID   => $merchantID,
-        terminalID   => $terminalID,
+        industryType    => 'EC',
+        transType       => 'AC',
+        bin             => $bin,
+        merchantID      => $merchantID,
+        terminalID      => $terminalID,
         ($_->payby eq 'CARD') ? (
-          ccAccountNum => $_->payinfo,
-          ccExp        => $_->expmmyy,
+          ccAccountNum    => $_->payinfo,
+          ccExp           => $_->expmmyy,
         ) : (
           ecpCheckRT      => ($_->payinfo =~ /@(\d+)/),
           ecpCheckDDA     => ($_->payinfo =~ /(\d+)@/),
-          ecpBankAcctType => $paytype{lc($_->cust_main->paytype)},
+          ecpBankAcctType => $paytype{lc($_->paytype)},
           ecpDelvMethod   => 'A',
         ),
-        avsZip          => substr($_->zip, 0, 10),
-        avsAddress1     => substr($_->address1, 0, 30),
-        avsAddress2     => substr($_->address2, 0, 30),
-        avsCity         => substr($_->city, 0, 20),
-        avsState        => $_->state,
-        avsName        => substr($_->first . ' ' . $_->last, 0, 30),
-        avsCountryCode => $_->country,
-        orderID        => $_->paybatchnum,
-        amount         => $_->amount * 100,
+                           # truncate_egc will die() on empty string
+        avsZip      => $_->zip      ? truncate_egc($_->zip,      10) : undef,
+        avsAddress1 => $_->address1 ? truncate_egc($_->address1, 30) : undef,
+        avsAddress2 => $_->address2 ? truncate_egc($_->address2, 30) : undef,
+        avsCity     => $_->city     ? truncate_egc($_->city,     20) : undef,
+        avsState    => $_->state    ? truncate_egc($_->state,     2) : undef,
+        avsName     => ($_->first || $_->last)
+                       ? truncate_egc($_->first. ' '. $_->last, 30) : undef,
+        ( $paymentech_countries{ $_->country }
+          ? ( avsCountryCode  => $_->country )
+          : ()
+        ),
+        orderID           => $_->paybatchnum,
+        amount            => $_->amount * 100,
         );
+      # only do this if recurringInd is enabled in config, 
+      # and the customer has at least one non-canceled recurring package
+      if ( $with_recurringInd and $status =~ /^active|suspended|ordered$/ ) {
+        # then send RF if this is the first payment on this payinfo,
+        # RS otherwise.
+        $order{'recurringInd'} = $_->payinfo_used ? 'RS' : 'RF';
+      }
       foreach my $key (keys %order) {
         $xml->dataElement($key, $order{$key})
       }
@@ -148,10 +173,16 @@ sub _upgrade_gateway {
   my $conf = FS::Conf->new;
   my @batchconfig = $conf->config('batchconfig-paymentech');
   my %options;
-  @options{ qw(bin terminalID merchantID login password ) } = @batchconfig;
+  @options{ qw(
+    bin
+    terminalID
+    merchantID
+    login
+    password
+    with_recurringInd
+  ) } = @batchconfig;
   $options{'industryType'} = 'EC';
   ( 'Paymentech', %options );
 }
 
 1;
-