TD EFT batch format, RT#10545
authormark <mark>
Fri, 12 Nov 2010 23:33:53 +0000 (23:33 +0000)
committermark <mark>
Fri, 12 Nov 2010 23:33:53 +0000 (23:33 +0000)
FS/FS/Conf.pm
FS/FS/pay_batch.pm
FS/FS/pay_batch/td_eft1464.pm [new file with mode: 0644]
httemplate/search/cust_pay_batch.cgi

index a3f1b54..8267b9b 100644 (file)
@@ -2898,6 +2898,13 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'batchconfig-td_eft1464',
+    'section'     => 'billing',
+    'description' => 'Configuration for TD Bank EFT1464 batching, five lines: 1. Originator ID, 2. Datacenter Code, 3. Short name, 4. Long name, 5. Returned payment branch number, 6. Returned payment account, 7. Transaction code.',
+    'type'        => 'textarea',
+  },
+
+  {
     'key'         => 'payment_history-years',
     'section'     => 'UI',
     'description' => 'Number of years of payment history to show by default.  Currently defaults to 2.',
index b5ef85b..d142411 100644 (file)
@@ -202,9 +202,10 @@ sub import_results {
 
   my $conf = new FS::Conf;
 
-  my $filetype            = $info->{'filetype'};      # CSV or fixed
+  my $filetype            = $info->{'filetype'};      # CSV, fixed, variable
   my @fields              = @{ $info->{'fields'}};
   my $formatre            = $info->{'formatre'};      # for fixed
+  my $parse               = $info->{'parse'};         # for variable
   my @all_values;
   my $begin_condition     = $info->{'begin_condition'};
   my $end_condition       = $info->{'end_condition'};
@@ -213,6 +214,7 @@ sub import_results {
   my $hook                = $info->{'hook'};
   my $approved_condition  = $info->{'approved'};
   my $declined_condition  = $info->{'declined'};
+  my $close_condition     = $info->{'close_condition'};
 
   my $csv = new Text::CSV_XS;
 
@@ -286,7 +288,17 @@ sub import_results {
         };
         push @values, $line;
         push @all_values, \@values;
-      }else{
+      }
+      elsif ($filetype eq 'variable') {
+        my @values = ( eval { $parse->($self, $line) } );
+        if( $@ ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $@;
+        };
+        push @values, $line;
+        push @all_values, \@values;
+      }
+      else {
         $dbh->rollback if $oldAutoCommit;
         return "Unknown file type $filetype";
       }
@@ -420,7 +432,20 @@ sub import_results {
     }
 
   }
-  
+
+  if ( defined($close_condition) ) {
+    # Allow the module to decide whether to close the batch.
+    # This is used for TD EFT, which requires two imports before 
+    # closing.
+    # $close_condition can also die() to abort the whole import.
+    my $close = eval { $close_condition->($self) };
+    if ( $@ ) {
+      $dbh->rollback;
+      die $@;
+    }
+    $self->set_status('I') if !$close;
+  }
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 
diff --git a/FS/FS/pay_batch/td_eft1464.pm b/FS/FS/pay_batch/td_eft1464.pm
new file mode 100644 (file)
index 0000000..1fbf2ad
--- /dev/null
@@ -0,0 +1,229 @@
+package FS::pay_batch::td_eft1464;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Date::Format 'time2str';
+use FS::Conf;
+use FS::Record qw(qsearch);
+
+=head1 NAME
+
+td_eft1464 - TD Commercial Banking EFT1464 format
+
+=head1 CONFIGURATION
+
+The Freeside option 'batchconfig-td_eft1464' must be set 
+with the following values on separate lines:
+
+=over 4
+
+=item Originator ID
+
+=item TD Datacenter Location
+
+00400 - Vancouver
+00410 - Montreal
+00420 - Toronto
+00430 - Halifax
+00470 - Winnipeg
+00490 - Calgary
+
+=item Short Name
+
+=item Long Name
+
+=item Returned Payment Branch (5 digits)
+
+=item Returned Payment Account
+
+=item Transaction Type Code - defaults to "437" (Internet access)
+
+=back
+
+=cut
+
+my $conf;
+my %opt;
+my $i;
+
+$name = 'td_eft1464';
+# TD Bank EFT 1464 Byte format
+
+%import_info = (
+  'filetype'    => 'variable',
+  'parse'       => \&parse,
+  'fields' => [ qw(
+    status
+    paid
+    paybatchnum
+    ) ],
+  'hook' => sub {
+      my $hash = shift;
+      $hash->{'_date'} = time;
+      $hash->{'paid'} = sprintf('%.2f', $hash->{'paid'});
+  },
+  'approved'    => sub { 
+      my $hash = shift;
+      $hash->{'status'} eq 'A'
+  },
+  'declined'    => sub {
+      my $hash = shift;
+      $hash->{'status'} eq 'D';
+  },
+  'begin_condition' => sub {
+      my $hash = shift;
+      $hash->{'status'} eq 'A' or $hash->{'status'} eq 'D';
+  },
+  'end_condition' => sub {
+      my $hash = shift;
+      $hash->{'status'} eq 'END'
+  },
+  'close_condition' => sub {
+      my $batch = shift;
+      my @cust_pay_batch = qsearch('cust_pay_batch', 
+        { batchnum => $batch->batchnum }
+      );
+      return ( (grep {! length($_->status) } @cust_pay_batch) == 0 );
+  },
+);
+
+sub parse {
+  my ($batch, $line) = @_;
+  $batch->setfield('import_state','') if !$batch->import_state;
+  return 'END' if $batch->import_state eq 'END';
+  if( $batch->import_state eq '212' ) {
+    # APX212 fields:
+    # trace number, trans type, amount, due date, routing number, 
+    # account number, xref number, return routing number and account
+    # The only ones we take are amount and xref number.
+    if( $line =~ /CREDITS\s+DEBITS/ ) {
+      $batch->setfield('import_state', 'END');
+      return 'END';
+    }
+    $line =~ /^\d{22} D\d{3} (.{14})    \d{5}  \d{4}-\d{5}  .{12}    (.{19}).*$/
+      or die "can't parse: $line";
+    # strip leading zeroes/spaces from paybatchnum at this point
+    return ('A', $1, sprintf('%u',$2));
+  }
+  elsif( $batch->import_state eq '234' ) {
+    # APX234 fields:
+    # payor name, xref number, due date, routing number, account number,
+    # amount, reason for return
+    if( $line =~ /TOTAL NUMBER -/ ) {
+      $batch->setfield('import_state', 'END');
+      return 'END';
+    }
+    $line =~ /^.{22} (.{19})   \d\d\/\d\d\/\d\d  \d{9}  .{12} (.{14}).*$/
+      or die "can't parse: $line";
+    return ('D', $2, sprintf('%u',$1));
+  }
+  else {
+    if ( $line =~ /ITEM TRACE NUMBER/ ) {
+      $batch->setfield('import_state','212');
+    }
+    elsif ( $line =~ /REASON FOR RETURN/ ) {
+      $batch->setfield('import_state','234');
+    } # else leave it undefined
+    return 'HEADER';
+  }
+}
+
+%export_info = (
+  init => sub {
+    $conf = shift;
+    @opt{
+      'origid',
+      'datacenter',
+      'shortname',
+      'longname',
+      'retbranch',
+      'retacct',
+      'cpacode',
+    } = $conf->config("batchconfig-td_eft1464");
+    $opt{'origid'} = sprintf('%-10s', $opt{'origid'});
+    $opt{'shortname'} = sprintf('%-15s', $opt{'shortname'});
+    $opt{'longname'} = sprintf('%-30s', $opt{'longname'});
+    $opt{'retbranch'} = '0004'.sprintf('%5s',$opt{'retbranch'});
+    $opt{'retacct'} = sprintf('%-11s', $opt{'retacct'}). ' ';
+    $i = 1;
+  },
+  header => sub { 
+    my $pay_batch = shift;
+    my @cust_pay_batch = @{(shift)};
+    my $time = $pay_batch->download || time;
+    my $now = sprintf("%03u%03u", 
+      (localtime(time))[5],#year since 1900
+      (localtime(time))[7]+1);#day of year
+
+    # Request settlement the next day
+    my $duedate = time+86400;
+    $opt{'due'} = sprintf("%03u%03u",
+      (localtime($duedate))[5],
+      (localtime($duedate))[7]+1);
+
+    $opt{'fcn'} = 
+      sprintf('%04u', ($pay_batch->batchnum % 9999)+1), # file creation number
+    join('',
+      'A', #record type
+      sprintf('%09u', 1), #record number
+      $opt{'origid'},
+      $opt{'fcn'},
+      $now,
+      $opt{'datacenter'},
+      ' ' x 1429 #filler
+    );
+  },
+  row => sub {
+    my ($cust_pay_batch, $pay_batch) = @_;
+    my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+    $i++;
+    # The 1464 byte format supports up to 5 payments per line,
+    # but we're only going to send 1.
+    my $control = join('',
+      'D',                  # for 'debit'
+      sprintf("%09u", $i),  #record number
+      $opt{'origid'},
+      $opt{'fcn'},
+    );
+    my $payment = join('',
+      $opt{'cpacode'} || 437, # CPA code, defaults to "Internet access"
+      sprintf('%010.0f', $cust_pay_batch->amount*100),
+      $opt{'due'}, #due date...? XXX
+      sprintf('%09u', $aba),
+      sprintf('%-12s', $account),
+      ' ' x 22,
+      ' ' x 3,
+      $opt{'shortname'},
+      sprintf('%-30s', 
+        join(' ',
+          $cust_pay_batch->first, $cust_pay_batch->last)
+      ),
+      $opt{'longname'},
+      $opt{'origid'},
+      sprintf('%-19s', $cust_pay_batch->paybatchnum), # originator reference num
+      $opt{'retbranch'},
+      $opt{'retacct'}, 
+      ' ' x 22,
+      ' ' x 2,
+      '0' x 11,
+    );
+    return $control . $payment . (' ' x 720);
+  },
+  footer => sub {
+    my ($pay_batch, $batchcount, $batchtotal) = @_;
+    join('',
+      'Z',
+      sprintf('%09u', $batchcount + 2),
+      $opt{'origid'}, 
+      $opt{'fcn'},
+      sprintf('%014.0f', $batchtotal*100), # total of debit txns
+      sprintf('%08u', $batchcount), # number of debit txns
+      '0' x 14, # total of credit txns
+      '0' x 8, # total of credit txns
+      ' ' x 1396,
+    )
+  },
+);
+
+1;
+
index 825d21c..df635ee 100755 (executable)
@@ -150,6 +150,8 @@ if ( $pay_batch ) {
                     qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
                     qq!<OPTION VALUE="paymentech">Chase Paymentech XML</OPTION>!.
                     qq!<OPTION VALUE="RBC">Royal Bank of Canada PDS</OPTION>!.
+                    qq!<OPTION VALUE="td_eft1464">TD Commercial Banking EFT 1464 byte</OPTION>!.
+
                     qq!</SELECT>!;
     }
     $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR><BR></TR>!;
@@ -191,6 +193,7 @@ if ( $pay_batch ) {
                     qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
                     qq!<OPTION VALUE="paymentech">Chase Paymentech XML</OPTION>!.
                     qq!<OPTION VALUE="RBC">Royal Bank of Canada PDS</OPTION>!.
+                    qq!<OPTION VALUE="td_eft1464">TD Commercial Banking EFT 1464 byte</OPTION>!.
                     qq!</SELECT><BR></TR>!;
     }
     $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum">!;