summaryrefslogtreecommitdiff
path: root/FS/FS/pay_batch
diff options
context:
space:
mode:
Diffstat (limited to 'FS/FS/pay_batch')
-rw-r--r--FS/FS/pay_batch/BoM.pm73
-rw-r--r--FS/FS/pay_batch/PAP.pm103
-rw-r--r--FS/FS/pay_batch/ach_spiritone.pm65
-rw-r--r--FS/FS/pay_batch/chase_canada.pm104
-rw-r--r--FS/FS/pay_batch/paymentech.pm114
-rw-r--r--FS/FS/pay_batch/td_canada_trust.pm104
6 files changed, 563 insertions, 0 deletions
diff --git a/FS/FS/pay_batch/BoM.pm b/FS/FS/pay_batch/BoM.pm
new file mode 100644
index 0000000..7bfc22a
--- /dev/null
+++ b/FS/FS/pay_batch/BoM.pm
@@ -0,0 +1,73 @@
+package FS::pay_batch::BoM;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'BoM';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [],
+ 'hook' => sub { die "Can't import BoM" },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid,
+ $datacenter,
+ $typecode,
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct) = $conf->config("batchconfig-BoM");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( "A%10s%04u%06u%05u%54s\n",
+ $origid,
+ $pay_batch->batchnum,
+ jdate($pay_batch->download),
+ $datacenter,
+ "") .
+ sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",
+ $typecode,
+ jdate($pay_batch->download),
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct);
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",
+ $cust_pay_batch->amount * 100,
+ $aba,
+ $account,
+ $cust_pay_batch->payname,
+ $cust_pay_batch->paybatchnum
+ );
+ },
+ footer => sub {
+ my ($pay_batch, $batchcount, $batchtotal) = @_;
+ sprintf( "YD%08u%014.0f%56s\n", $batchcount, $batchtotal*100, "").
+ sprintf( "Z%014u%04u%014u%05u%41s\n",
+ $batchtotal*100, $batchcount, "0", "0", "");
+ },
+);
+
+sub jdate {
+ my (@date) = localtime(shift);
+ sprintf("%03d%03d", $date[5] % 100, $date[7] + 1);
+}
+
+1;
+
diff --git a/FS/FS/pay_batch/PAP.pm b/FS/FS/pay_batch/PAP.pm
new file mode 100644
index 0000000..432ef07
--- /dev/null
+++ b/FS/FS/pay_batch/PAP.pm
@@ -0,0 +1,103 @@
+package FS::pay_batch::PAP;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'PAP';
+
+%import_info = (
+ 'filetype' => 'fixed',
+ 'formatre' => '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$',
+ 'fields' => [
+ 'recordtype',
+ 'batchnum',
+ 'datacenter',
+ 'paid',
+ '_date',
+ 'bank',
+ 'payinfo',
+ 'paybatchnum',
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
+ my $tmpdate = timelocal( 0,0,1,1,0,substr($hash->{'_date'}, 0, 3)+2000);
+ $tmpdate += 86400*(substr($hash->{'_date'}, 3, 3)-1) ;
+ $hash->{'_date'} = $tmpdate;
+ $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'};
+ },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+# Why does pay_batch.pm have approved_condition and declined_condition?
+# It doesn't even try to handle the case of neither condition being met.
+ 'end_hook' => sub {
+ my( $hash, $total) = @_;
+ $total = sprintf("%.2f", $total);
+ my $batch_total = $hash->{'datacenter'}.$hash->{'paid'}.
+ substr($hash->{'_date'},0,1); # YUCK!
+ $batch_total = sprintf("%.2f", $batch_total / 100 );
+ return "Our total $total does not match bank total $batch_total!"
+ if $total != $batch_total;
+ '';
+ },
+ 'end_condition' => sub {
+ my $hash = shift;
+ $hash->{recordtype} eq 'W';
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid,
+ $datacenter,
+ $typecode,
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct) = $conf->config("batchconfig-PAP");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",
+ $origid,
+ $typecode,
+ cdate($pay_batch->download),
+ $shortname,
+ $mybank,
+ $myacct,
+ $pay_batch->batchnum,
+ "" )
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",
+ $cust_pay_batch->payname,
+ cdate($pay_batch->download),
+ $cust_pay_batch->paybatchnum,
+ $aba,
+ $account,
+ $cust_pay_batch->amount*100 );
+ },
+ footer => sub {
+ my ($pay_batch, $batchcount, $batchtotal) = @_;
+ sprintf( "T%08u%014.0f%57s\n",
+ $batchcount,
+ $batchtotal*100,
+ "" );
+ },
+);
+
+sub cdate {
+ my (@date) = localtime(shift);
+ sprintf("%02d%02d%02d", $date[3], $date[4] + 1, $date[5] % 100);
+}
+
+1;
+
diff --git a/FS/FS/pay_batch/ach_spiritone.pm b/FS/FS/pay_batch/ach_spiritone.pm
new file mode 100644
index 0000000..bd3bb14
--- /dev/null
+++ b/FS/FS/pay_batch/ach_spiritone.pm
@@ -0,0 +1,65 @@
+package FS::pay_batch::ach_spiritone;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+use File::Temp;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'ach-spiritone'; # note spelling
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ '', #name
+ 'paybatchnum',
+ 'aba',
+ 'payinfo',
+ '', #transaction type
+ 'paid',
+ '', #default transaction type
+ '', #default amount
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ $hash->{'_date'} = time;
+ $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'aba'};
+ },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+);
+
+%export_info = (
+# This is the simplest case.
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ my $payname = $cust_pay_batch->first . ' ' . $cust_pay_batch->last;
+ $payname =~ tr/",/ /;
+ qq!"$payname","!.$cust_pay_batch->paybatchnum.
+ qq!","$aba","$account","27","!.$cust_pay_batch->amount.
+ qq!","27","0.00"!; #"
+ },
+ autopost => sub {
+ my ($pay_batch, $batch) = @_;
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp(
+ TEMPLATE => 'paybatch.'. $pay_batch->batchnum .'.XXXXXXXX',
+ DIR => $dir,
+ ) or return "can't open temp file: $!\n";
+
+ print $fh $batch;
+ seek $fh, 0, 0;
+
+ my $error = $pay_batch->import_results( 'filehandle' => $fh,
+ 'format' => $name,
+ );
+ return $error if $error;
+ },
+);
+
+1;
+
diff --git a/FS/FS/pay_batch/chase_canada.pm b/FS/FS/pay_batch/chase_canada.pm
new file mode 100644
index 0000000..909e4ae
--- /dev/null
+++ b/FS/FS/pay_batch/chase_canada.pm
@@ -0,0 +1,104 @@
+package FS::pay_batch::chase_canada;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my $origid;
+
+$name = 'csv-chase_canada-E-xactBatch';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ '',
+ '',
+ '',
+ 'paid',
+ 'auth',
+ 'payinfo',
+ '',
+ '',
+ 'bankcode',
+ 'bankmess',
+ 'etgcode',
+ 'etgmess',
+ '',
+ 'paybatchnum',
+ '',
+ 'result',
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ my $cpb = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} );
+ $hash->{'_date'} = time;
+ $hash->{'payinfo'} = $cpb->{'payinfo'}
+ if( substr($hash->{'payinfo'}, -4) eq substr($cpb->{'payinfo'}, -4) );
+ },
+ 'approved' => sub {
+ my $hash = shift;
+ $hash->{'etgcode'} eq '00' && $hash->{'result'} eq 'Approved';
+ },
+ 'declined' => sub {
+ my $hash = shift;
+ $hash->{'etgcode'} ne '00' || $hash->{'result'} eq 'Declined';
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid) = $conf->config("batchconfig-$name");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( '$$E-xactBatchFileV1.0$$%s:%03u$$%s',
+ sdate($pay_batch->download),
+ $pay_batch->batchnum,
+ $origid );
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my $payname = $cust_pay_batch->payname;
+ $payname =~ tr/",/ /;
+
+ join(',',
+ $cust_pay_batch->paybatchnum,
+ $cust_pay_batch->custnum,
+ $cust_pay_batch->invnum,
+ qq!"$payname"!,
+ '00',
+ $cust_pay_batch->payinfo,
+ $cust_pay_batch->amount,
+ expdate($cust_pay_batch->exp),
+ '',
+ ''
+ );
+ },
+ # no footer
+);
+
+sub sdate {
+ my (@date) = localtime(shift);
+ sprintf('%02d/%02d/%02d', $date[5] % 100, $date[4] + 1, $date[3]);
+}
+
+sub expdate {
+ my $exp = shift;
+ $exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my ($mon, $y) = ($2, $1);
+ if($conf->exists('batch-increment_expiration')) {
+ my ($curmon, $curyear) = (localtime(time))[4,5];
+ $curmon++;
+ $curyear -= 100;
+ $y++ while $y < $curyear || ($y == $curyear && $mon < $curmon);
+ }
+ $mon = "0$mon" if $mon =~ /^\d$/;
+ $y = "0$y" if $y =~ /^\d$/;
+ return "$mon$y";
+}
+
+1;
diff --git a/FS/FS/pay_batch/paymentech.pm b/FS/FS/pay_batch/paymentech.pm
new file mode 100644
index 0000000..44fa78a
--- /dev/null
+++ b/FS/FS/pay_batch/paymentech.pm
@@ -0,0 +1,114 @@
+package FS::pay_batch::paymentech;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local;
+use Date::Format 'time2str';
+use Date::Parse 'str2time';
+use FS::Conf;
+
+my $conf;
+my ($bin, $merchantID, $terminalID, $username);
+$name = 'paymentech';
+
+%import_info = (
+ filetype => 'XML',
+ xmlrow => [ qw(transResponse newOrderResp) ],
+ fields => [
+ 'paybatchnum',
+ '_date',
+ 'approvalStatus',
+ ],
+ xmlkeys => [
+ 'orderID',
+ 'respDateTime',
+ 'approvalStatus',
+ ],
+ 'hook' => sub {
+ my ($hash, $oldhash) = @_;
+ my ($mon, $day, $year, $hour, $min, $sec) =
+ $hash->{'_date'} =~ /^(..)(..)(....)(..)(..)(..)$/;
+ $hash->{'_date'} = timelocal($sec, $min, $hour, $day, $mon-1, $year);
+ $hash->{'paid'} = $oldhash->{'amount'};
+ },
+ 'approved' => sub { my $hash = shift;
+ $hash->{'approvalStatus'}
+ },
+ 'declined' => sub { my $hash = shift;
+ ! $hash->{'approvalStatus'}
+ },
+);
+
+my %paytype = (
+ 'personal checking' => 'C',
+ 'personal savings' => 'S',
+ 'business checking' => 'X',
+ 'business savings' => 'X',
+ );
+
+%export_info = (
+ init => sub {
+# Load this at run time
+ eval "use XML::Simple";
+ die $@ if $@;
+ my $conf = shift;
+ ($bin, $terminalID, $merchantID, $username) =
+ $conf->config('batchconfig-paymentech');
+ },
+# Here we do all the work in the header function.
+ header => sub {
+ my $pay_batch = shift;
+ my @cust_pay_batch = @{(shift)};
+ my $count = 0;
+ XML::Simple::XMLout( {
+ transRequest => {
+ RequestCount => scalar(@cust_pay_batch),
+ batchFileID => {
+ userID => $username,
+ fileDateTime => time2str('%Y%m%d%H%M%s',time),
+ fileID => 'batch'.time2str('%Y%m%d',time),
+ },
+ newOrder => [ map { {
+ # $_ here refers to a cust_pay_batch record.
+ BatchRequestNo => $count++,
+ industryType => 'EC',
+ transType => 'AC',
+ bin => $bin,
+ merchantID => $merchantID,
+ terminalID => $terminalID,
+ ($_->payby eq 'CARD') ? (
+ # Credit card stuff
+ ccAccountNum => $_->payinfo,
+ ccExp => time2str('%y%m',str2time($_->exp)),
+ ) : (
+ # ECP (electronic check) stuff
+ ecpCheckRT => ($_->payinfo =~ /@(\d+)/),
+ ecpCheckDDA => ($_->payinfo =~ /(\d+)@/),
+ ecpBankAcctType => $paytype{lc($_->cust_main->paytype)},
+ ecpDelvMethod => 'B'
+ ),
+ avsZip => $_->zip,
+ avsAddress1 => $_->address1,
+ avsAddress2 => $_->address2,
+ avsCity => $_->city,
+ avsState => $_->state,
+ avsName => $_->first . ' ' . $_->last,
+ avsCountryCode => $_->country,
+ orderID => $_->paybatchnum,
+ amount => $_->amount * 100,
+ } } @cust_pay_batch
+ ],
+ endOfDay => {
+ BatchRequestNo => $count++,
+ bin => $bin,
+ merchantID => $merchantID,
+ terminalID => $terminalID
+ },
+ }
+ }, KeepRoot => 1, NoAttr => 1);
+ },
+ row => sub {},
+);
+
+1;
+
diff --git a/FS/FS/pay_batch/td_canada_trust.pm b/FS/FS/pay_batch/td_canada_trust.pm
new file mode 100644
index 0000000..43b9237
--- /dev/null
+++ b/FS/FS/pay_batch/td_canada_trust.pm
@@ -0,0 +1,104 @@
+package FS::pay_batch::td_canada_trust;
+
+# Formerly known as csv-td_canada_trust-merchant_pc_batch,
+# which I'm sure we can all agree is both a terrible name
+# and an illegal Perl identifier.
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'csv-td_canada_trust-merchant_pc_batch';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ 'paybatchnum',
+ 'paid',
+ '', # card type
+ '_date',
+ 'time',
+ 'payinfo',
+ '', # expiry date
+ '', # auth number
+ 'type', # transaction type
+ 'result', # processing result
+ '', # terminal ID
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ my $date = $hash->{'_date'};
+ my $time = $hash->{'time'};
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100);
+ $hash->{'_date'} = timelocal( substr($time, 4, 2),
+ substr($time, 2, 2),
+ substr($time, 0, 2),
+ substr($date, 6, 2),
+ substr($date, 4, 2)-1,
+ substr($date, 0, 4)-1900 );
+ },
+ 'approved' => sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && $hash->{'result'} == 3
+ },
+ 'declined' => sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && ( $hash->{'result'} == 4
+ || $hash->{'result'} == 5 )
+ },
+ 'end_condition' => sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0BC';
+ },
+ 'end_hook' => sub {
+ my ($hash, $total) = @_;
+ $total = sprintf("%.2f", $total);
+ my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100);
+ return "Our total $total does not match bank total $batch_total!"
+ if $total != $batch_total;
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ },
+ # no header
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+
+ return join(',',
+ '',
+ '',
+ '',
+ '',
+ $cust_pay_batch->payinfo,
+ expdate($cust_pay_batch->exp),
+ $cust_pay_batch->amount,
+ $cust_pay_batch->paybatchnum
+ );
+ },
+# no footer
+);
+
+sub expdate {
+ my $exp = shift;
+ $exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my ($mon, $y) = ($2, $1);
+ if($conf->exists('batch-increment_expiration')) {
+ my ($curmon, $curyear) = (localtime(time))[4,5];
+ $curmon++;
+ $curyear -= 100;
+ $y++ while $y < $curyear || ($y == $curyear && $mon < $curmon);
+ }
+ $mon = "0$mon" if $mon =~ /^\d$/;
+ $y = "0$y" if $y =~ /^\d$/;
+ return "$mon$y";
+}
+
+1;
+