debian packaging
[Business-BatchPayment-TD_EFT.git] / TD_EFT.pm
index ba9ee9f..494f216 100644 (file)
--- a/TD_EFT.pm
+++ b/TD_EFT.pm
@@ -31,7 +31,14 @@ my $processor = Business::BatchPayment->processor('TD_EFT',
   return_branch   => '10202',   # 0004 + 5-digit branch number
   return_account  => '00124598951', # 11 digits
   cpa_code        => '120',
-  counter         => 101,
+  # optional, for SFTP file transport
+  host            => '192.168.100.1',
+  login           => 'mylogin',
+  password        => 'mypassword',
+  # optional, for encryption
+  encrypt_cmd     => 'compx ASCII DE3',
+  decrypt_cmd     => 'decompx',
+  encrypt_key     => '/path/to/encrypt.key',
 );
 
 my $result = $processor->submit(@items);
@@ -57,8 +64,31 @@ to use for returned payments.
 
 =item cpa_code - Your 3-digit CPA industry code.
 
-=item counter - Batch sequence number.  You MUST save this after submitting
-a batch, and pass the same value back in in the next session.
+=back
+
+=head2 TRANSPORT ATTRIBUTES
+
+These need to be specified only if you're using the SFTP transport.
+
+=over 4
+
+=item host - SFTP hostname
+
+=item login - SFTP username
+
+=item password - SFTP password
+
+=item encrypt_cmd - Command to use to encrypt/compress batches before 
+sending.  This will be called, somewhat awkwardly, with the name of the 
+output file inserted as the first argument, and the name of the input 
+file (the plaintext) passed on standard input.  If empty, batches will
+be sent as plaintext.
+
+=item descrypt_cmd - Command to decrypt/decompress downloaded batches.
+
+=item encrypt_key - File to copy into the working directory before 
+running encrypt/decrypt commands.  COMPX requires the encryption key
+to be supplied like this.
 
 =back
 
@@ -85,11 +115,12 @@ has 'datacentre' => (
   required => 1,
 );
 
-has 'counter' => (
+has [ qw( login password host encrypt_cmd encrypt_key decrypt_cmd ) ] =>
+(
   is => 'rw',
-  isa => 'Int',
-  default => 0,
-);
+  isa => 'Str',
+  required => 0,
+); # only required for the default transport
 
 sub BUILD {
   my $self = shift;
@@ -97,33 +128,47 @@ sub BUILD {
   $self->originator(     sprintf('%-10.10s', $self->originator)     );
   $self->short_name(     sprintf('%-15.15s', $self->short_name)     );
   $self->long_name(      sprintf('%-30.30s', $self->long_name)      );
-  $self->return_account( sprintf('%-11.11s', $self->return_account) );
+  $self->return_account( sprintf('%-11.11s', $self->return_account) . ' ' );
   $self->return_branch ( '0004'.
     sprintf('%-5.5s', $self->return_branch) )
     unless length($self->return_branch) == 9;
 }
 
+sub default_transport {
+  my $self = shift;
+  Business::BatchPayment->create('TD_EFT::Transport' =>
+    login     => $self->login,
+    password  => $self->password,
+    host      => $self->host,
+    put_path  => 'APXBA807/',
+    debug     => $self->debug,
+    encrypt_cmd => $self->encrypt_cmd,
+    decrypt_cmd => $self->decrypt_cmd,
+    encrypt_key => $self->encrypt_key,
+  );
+}
+
+
 before format_request => sub {
   my ($self, $batch) = @_;
   #shazam!
   Business::BatchPayment::TD_EFT::Batch->meta->apply($batch);
   
   my $dt = DateTime->now;
+  $dt->set_time_zone('local');
   $batch->create_date(sprintf('%03d%03d', $dt->year % 1000, $dt->day_of_year));
 
+  my $counter = $batch->batch_id;
+  $counter = 0 unless $counter =~ /^\d+$/;
   # a number from 1 to 9999
-  $batch->fcn(sprintf('%04u', ($self->counter % 9999) + 1));
-  # what should be a unique identifier
-  $batch->batch_id($batch->create_date . '-' . $batch->fcn);
-};
-
-after submit => sub {
-  my $self = shift;
-  $self->counter($self->counter + 1);
+  $batch->fcn(sprintf('%04u', ($counter % 9999) + 1));
+  # We can't return the FCN as the batch_id because it wraps around.
+  # The TIDs are still correct though.
 };
 
 sub format_header {
   my ($self, $batch) = @_;
+  $batch->row(1);
   my $header =
     'A' .          #record type
     '000000001' .  #row number
@@ -138,7 +183,10 @@ sub format_item {
   my ($self, $item, $batch) = @_;
 
   $batch->row($batch->row + 1);
-  my $cents = int($item->amount * 100);
+  # Avoid floating point error: if we're passed a non-exact number of
+  # cents (and we will be...), round to the nearest integer, and then
+  # sum integer numbers of cents to get the batch total.
+  my $cents = sprintf('%.0f',$item->amount * 100);
   if ( $item->action eq 'payment' ) {
     $batch->total_payment( $batch->total_payment + $cents );
     $batch->count_payment( $batch->count_payment + 1 );
@@ -151,7 +199,11 @@ sub format_item {
   # (should this use Time::Business?  Date::Holidays::CA?)
   # (should we just require the merchant to specify a process date?)
   my $process_date = $item->process_date;
-  $process_date ||= DateTime->today->add(days => 1);
+  if ( $process_date ) {
+    $process_date->set_time_zone('local');
+  } else {
+    $process_date = DateTime->today->set_time_zone('local')->add(days => 1);
+  }
 
   my $duedate = sprintf('%03d%03d', 
     $process_date->year % 1000,
@@ -256,13 +308,12 @@ sub parse_ack_264 {
         # Most of these fields aren't interesting to us.
         $batch->fcn($4);
         $batch->create_date($5);
-        $batch->batch_id($batch->create_date . '-' . $batch->fcn);
         my $date = $10;
         $date =~ /^(....)-(..)-(..)$/; # actual process date, YYYY-MM-DD
         $payment_date = DateTime->new(year => $1, month => $2, day => $3);
       } elsif ( $row =~ /^[CD]/ ) {
         # Rejected item detail.
-        my @f = ($row =~ /^(.{1})(.{9})(.{14})(.{3})(.{10})(.{6})(.{9})(.{12})(.{25})(.{15})(.{30})(.{30})(.{10})(.{19})(.{9})(.{12})(.{15})(.{22})(.{2})(.{11})$/)
+        my @f = ($row =~ /^(.{1})(.{9})(.{14})(.{3})(.{10})(.{6})(.{9})(.{12})(.{22})(.{3})(.{15})(.{30})(.{30})(.{10})(.{19})(.{9})(.{12})(.{15})(.{22})(.{2})(.{11})$/)
           or die "invalid detail row\n";
         foreach (@f) { s/^\s+//; s/\s+$//; }
         unshift @f,  ''; # make field numbers line up
@@ -281,8 +332,7 @@ sub parse_ack_264 {
           account_number  => $f[8],
           tid             => $f[15],
         );
-        my @error_fields = map { $field_order[$_] } ($f[21] =~ /../g);
-        # yuck
+        my @error_fields = map { $field_order[$_] } ($f[21] =~ /../g) if $f[21];
         $item->error_message('invalid fields: '.join(', ', @error_fields));
         $batch->push($item);
       } elsif ( $row =~ /^R/ ) {
@@ -335,12 +385,15 @@ sub parse_ret_80 {
     try {
       if ( $row =~ /^H/ ) {
         # Header.
-        $row =~ /^(.{1})(.{10})(.{1})( {3})(.{6})(.{30})(.{12})( {8})$/
+        $row =~ /^(.{1})(.{10})(.{1})( {3})(.{6})(.{30})(.{9})(.{12})( {8})$/
           or die "invalid header row\n";
         # the only field we care about is payment vs. credit
         # and even that only minimally
-        if ( $3 eq 'I' ) { $action = 'credit' }
-        elsif ( $3 eq 'J') { $action = 'payment' }
+        if ( $3 eq 'I' ) {
+          $action = 'credit'
+        } elsif ( $3 eq 'J') {
+          $action = 'payment'
+        }
       } elsif ( $row =~ /^D/ ) {
         # Detail.
         my @f = ( $row =~ /^(.{1})(.{20})(.{2})(.{1})(.{6})(.{19})(.{9})(.{12})(.{10})$/ )
@@ -364,9 +417,9 @@ sub parse_ret_80 {
       }
     } catch {
       $self->parse_error($row, $_);
-    }
+    };
     die "no valid header row found\n" unless $action;
-  }
+  } #foreach $row
   $batch;
 }
 
@@ -375,11 +428,10 @@ use Moose::Role;
 use List::Util qw(sum);
 
 has [qw( create_date fcn )] => ( is => 'rw', isa => 'Str' );
+# XXX use the "totals" method instead
 has [qw( row total_payment total_credit count_payment count_credit )] =>
   ( is => 'rw', isa => 'Int', default => 0 );
 
-=back
-
 =head1 AUTHOR
 
 Mark Wells, C<< <mark at freeside.biz> >>