add debian meta stuff
[Business-BatchPayment-Paymentech.git] / Paymentech.pm
index 09b5672..9eec935 100644 (file)
@@ -3,16 +3,12 @@ package Business::BatchPayment::Paymentech;
 use 5.006;
 use strict;
 use warnings;
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 =head1 NAME
 
 Business::BatchPayment::Paymentech - Chase Paymentech XML batch format.
 
-=head1 VERSION
-
-Version 0.01
-
 =head1 USAGE
 
 See L<Business::BatchPayment> for general usage notes.
@@ -69,16 +65,18 @@ use Moose;
 with 'Business::BatchPayment::Processor';
 with 'Business::BatchPayment::TestMode';
 
+use Encode;
+
 # could have some validation on all of these
 has [ qw(merchantID terminalID bin industryType login password) ] => (
-  is  => 'ro',
-  isa => 'Str',
+  is       => 'ro',
+  isa      => 'Str',
   required => 1,
 );
 
 has 'fileDateTime' => (
-  is => 'ro',
-  isa => 'Str',
+  is      => 'ro',
+  isa     => 'Str',
   default => sub {
     DateTime->now->strftime('%Y%m%d%H%M%S')
   },
@@ -98,7 +96,6 @@ sub default_transport {
   Business::BatchPayment::Paymentech::Transport->new(
     login     => $self->login,
     password  => $self->password,
-    put_path  => $self->fileDateTime,
     debug     => $self->debug,
     test_mode => $self->test_mode,
   );
@@ -110,9 +107,10 @@ sub format_request {
 
   my $output;
   my $xml = XML::Writer->new(
-    OUTPUT => \$output,
-    DATA_MODE => 1,
+    OUTPUT      => \$output,
+    DATA_MODE   => 1,
     DATA_INDENT => 2,
+    ENCODING    => 'utf-8',
   );
   $self->format_header($batch, $xml);
   my $count = 1;
@@ -132,11 +130,13 @@ sub format_header {
   my ($self, $batch, $xml) = @_;
   my $num_items = $batch->count;
 
+  $xml->xmlDecl();
   $xml->startTag('transRequest', RequestCount => $num_items + 1);
   $xml->startTag('batchFileID');
   $xml->dataElement(userID => $self->login);
   $xml->dataElement(fileDateTime => $self->fileDateTime);
-  $xml->dataElement(fileID => $self->fileDateTime);
+  $xml->dataElement(fileID => sprintf('%06d-', $batch->batch_id) . 
+                              $self->fileDateTime);
   $xml->endTag('batchFileID');
 }
 
@@ -145,25 +145,25 @@ sub format_item {
   if ( $item->action eq 'payment' ) {
     $xml->startTag('newOrder', BatchRequestNo => $count);
     my @order = (
-      industryType  => $self->industryType,
-      transType     => 'AC',
-      bin           => $self->bin,
-      merchantID    => $self->merchantID,
-      terminalID    => $self->terminalID,
+      industryType => $self->industryType,
+      transType    => 'AC',
+      bin          => $self->bin,
+      merchantID   => $self->merchantID,
+      terminalID   => $self->terminalID,
     );
     if ($item->payment_type eq 'CC') {
       push @order, (
-        ccAccountNum  => $item->card_number,
-        ccExp         => $item->expiration,
+        ccAccountNum => $item->card_number,
+        ccExp        => $item->expiration,
       );
     }
     elsif ( $item->payment_type eq 'ECHECK' ) {
       push @order, (
-        cardBrand     => 'EC',
-        ecpCheckRT    => $item->routing_code,
-        ecpCheckDDA   => $item->account_number,
+        cardBrand       => 'EC',
+        ecpCheckRT      => $item->routing_code,
+        ecpCheckDDA     => $item->account_number,
         ecpBankAcctType => $BankAcctType{ $item->account_type },
-        ecpDelvMethod => 'A',
+        ecpDelvMethod   => 'A',
       );
     }
     else {
@@ -171,15 +171,15 @@ sub format_item {
     }
     push @order, (
       avsZip         => $item->zip,
-      avsAddress1    => substr($item->address,   0, 30),
-      avsAddress2    => substr($item->address2,  0, 30),
-      avsCity        => substr($item->city,      0, 20),
-      avsState       => $item->state,
-      avsName        => substr($item->first_name .' '. $item->last_name, 0, 30),
-      avsCountryCode => ( $paymentech_countries{ $item->country }
-                            ? $_->country
-                            : ''
-                        ),
+      avsAddress1    => bytes_substr($item->address,   0, 30),
+      avsAddress2    => bytes_substr($item->address2,  0, 30),
+      avsCity        => bytes_substr($item->city,      0, 20),
+      avsState       => bytes_substr($item->state,     0, 2),
+      avsName        => bytes_substr($item->first_name. ' '. $item->last_name, 0, 30),
+      ( $paymentech_countries{ $item->country }
+        ? ( avsCountryCode  => $item->country )
+        : ()
+      ),
       orderID        => $item->tid,
       amount         => int( $item->amount * 100 ),
     );
@@ -244,17 +244,65 @@ sub parse_item {
     second  => $sec,
   );
 
+  my %failure_status = (
+    # API version 2.6, April 2013
+    '00'  => undef,       # Approved
+    '04'  => 'pickup',
+    '33'  => 'expired',
+    '41'  => 'stolen',
+    '42'  => 'inactive',
+    '43'  => 'stolen',
+    '44'  => 'inactive',
+    'B7'  => 'blacklisted', # Fraud
+    'B9'  => 'blacklisted', # On Negative File
+    'BB'  => 'stolen',      # Possible Compromise
+    'BG'  => 'blacklisted', # Blocked Account
+    'BQ'  => 'blacklisted', # Issuer has Flagged Account as Suspected Fraud
+    'C4'  => 'nsf',         # Over Credit Limit
+    'D5'  => 'blacklisted', # On Negative File
+    'D7'  => 'nsf',         # Insufficient Funds
+    'F3'  => 'inactive',    # Account Closed
+    'K6'  => 'nsf',         # NSF
+  ); # all others are "decline"
+
+  my $failure_status = undef;
+  my $error_message;
+
+  if ( $resp->{procStatus} ) {
+    $error_message = $resp->{procStatusMessage};
+  } elsif ( $resp->{respCode} ) {
+    $error_message = $resp->{respCodeMessage};
+    $failure_status = $failure_status{ $resp->{respCode} } || 'decline';
+  } else {
+    $error_message = '';
+  }
+
   my $item = Business::BatchPayment->create(Item =>
     tid           => $resp->{orderID},
     process_date  => $dt,
     authorization => $resp->{authorizationCode},
     order_number  => $resp->{txRefNum},
     approved      => ($resp->{approvalStatus} == 1),
-    error_message => $resp->{procStatusMessage},
+    error_message => $error_message,
+    failure_status  => $failure_status,
   );
   $item;
 }
 
+# internal use
+
+sub bytes_substr {
+  my ($string, $offset, $length, $repl) = @_;
+  my $bytes = substr(
+    Encode::encode('utf8', $string),
+    $offset,
+    $length,
+    Encode::encode('utf8', $repl)
+  );
+  return Encode::decode('utf8', $bytes, Encode::FB_QUIET);
+} 
+
+
 package Business::BatchPayment::Paymentech::Transport;
 
 use File::Temp qw( tempdir );
@@ -290,7 +338,8 @@ sub upload {
   my $self = shift;
   my $content = shift;
   my $tmpdir = tempdir( CLEANUP => 1 );
-  my $filename = $self->put_path; # also the value of the fileId tag
+  $content =~ /<fileID>(.*)<\/fileID>/;
+  my $filename = $1;
   my $archive_dir = $self->archive_to;
 
   warn "Writing temp file to $tmpdir/$filename.xml.\n" if $self->debug;