TD EFT batch format, RT#10545
[freeside.git] / FS / FS / pay_batch / td_eft1464.pm
1 package FS::pay_batch::td_eft1464;
2
3 use strict;
4 use vars qw(@ISA %import_info %export_info $name);
5 use Date::Format 'time2str';
6 use FS::Conf;
7 use FS::Record qw(qsearch);
8
9 =head1 NAME
10
11 td_eft1464 - TD Commercial Banking EFT1464 format
12
13 =head1 CONFIGURATION
14
15 The Freeside option 'batchconfig-td_eft1464' must be set 
16 with the following values on separate lines:
17
18 =over 4
19
20 =item Originator ID
21
22 =item TD Datacenter Location
23
24 00400 - Vancouver
25 00410 - Montreal
26 00420 - Toronto
27 00430 - Halifax
28 00470 - Winnipeg
29 00490 - Calgary
30
31 =item Short Name
32
33 =item Long Name
34
35 =item Returned Payment Branch (5 digits)
36
37 =item Returned Payment Account
38
39 =item Transaction Type Code - defaults to "437" (Internet access)
40
41 =back
42
43 =cut
44
45 my $conf;
46 my %opt;
47 my $i;
48
49 $name = 'td_eft1464';
50 # TD Bank EFT 1464 Byte format
51
52 %import_info = (
53   'filetype'    => 'variable',
54   'parse'       => \&parse,
55   'fields' => [ qw(
56     status
57     paid
58     paybatchnum
59     ) ],
60   'hook' => sub {
61       my $hash = shift;
62       $hash->{'_date'} = time;
63       $hash->{'paid'} = sprintf('%.2f', $hash->{'paid'});
64   },
65   'approved'    => sub { 
66       my $hash = shift;
67       $hash->{'status'} eq 'A'
68   },
69   'declined'    => sub {
70       my $hash = shift;
71       $hash->{'status'} eq 'D';
72   },
73   'begin_condition' => sub {
74       my $hash = shift;
75       $hash->{'status'} eq 'A' or $hash->{'status'} eq 'D';
76   },
77   'end_condition' => sub {
78       my $hash = shift;
79       $hash->{'status'} eq 'END'
80   },
81   'close_condition' => sub {
82       my $batch = shift;
83       my @cust_pay_batch = qsearch('cust_pay_batch', 
84         { batchnum => $batch->batchnum }
85       );
86       return ( (grep {! length($_->status) } @cust_pay_batch) == 0 );
87   },
88 );
89
90 sub parse {
91   my ($batch, $line) = @_;
92   $batch->setfield('import_state','') if !$batch->import_state;
93   return 'END' if $batch->import_state eq 'END';
94   if( $batch->import_state eq '212' ) {
95     # APX212 fields:
96     # trace number, trans type, amount, due date, routing number, 
97     # account number, xref number, return routing number and account
98     # The only ones we take are amount and xref number.
99     if( $line =~ /CREDITS\s+DEBITS/ ) {
100       $batch->setfield('import_state', 'END');
101       return 'END';
102     }
103     $line =~ /^\d{22} D\d{3} (.{14})    \d{5}  \d{4}-\d{5}  .{12}    (.{19}).*$/
104       or die "can't parse: $line";
105     # strip leading zeroes/spaces from paybatchnum at this point
106     return ('A', $1, sprintf('%u',$2));
107   }
108   elsif( $batch->import_state eq '234' ) {
109     # APX234 fields:
110     # payor name, xref number, due date, routing number, account number,
111     # amount, reason for return
112     if( $line =~ /TOTAL NUMBER -/ ) {
113       $batch->setfield('import_state', 'END');
114       return 'END';
115     }
116     $line =~ /^.{22} (.{19})   \d\d\/\d\d\/\d\d  \d{9}  .{12} (.{14}).*$/
117       or die "can't parse: $line";
118     return ('D', $2, sprintf('%u',$1));
119   }
120   else {
121     if ( $line =~ /ITEM TRACE NUMBER/ ) {
122       $batch->setfield('import_state','212');
123     }
124     elsif ( $line =~ /REASON FOR RETURN/ ) {
125       $batch->setfield('import_state','234');
126     } # else leave it undefined
127     return 'HEADER';
128   }
129 }
130
131 %export_info = (
132   init => sub {
133     $conf = shift;
134     @opt{
135       'origid',
136       'datacenter',
137       'shortname',
138       'longname',
139       'retbranch',
140       'retacct',
141       'cpacode',
142     } = $conf->config("batchconfig-td_eft1464");
143     $opt{'origid'} = sprintf('%-10s', $opt{'origid'});
144     $opt{'shortname'} = sprintf('%-15s', $opt{'shortname'});
145     $opt{'longname'} = sprintf('%-30s', $opt{'longname'});
146     $opt{'retbranch'} = '0004'.sprintf('%5s',$opt{'retbranch'});
147     $opt{'retacct'} = sprintf('%-11s', $opt{'retacct'}). ' ';
148     $i = 1;
149   },
150   header => sub { 
151     my $pay_batch = shift;
152     my @cust_pay_batch = @{(shift)};
153     my $time = $pay_batch->download || time;
154     my $now = sprintf("%03u%03u", 
155       (localtime(time))[5],#year since 1900
156       (localtime(time))[7]+1);#day of year
157
158     # Request settlement the next day
159     my $duedate = time+86400;
160     $opt{'due'} = sprintf("%03u%03u",
161       (localtime($duedate))[5],
162       (localtime($duedate))[7]+1);
163
164     $opt{'fcn'} = 
165       sprintf('%04u', ($pay_batch->batchnum % 9999)+1), # file creation number
166     join('',
167       'A', #record type
168       sprintf('%09u', 1), #record number
169       $opt{'origid'},
170       $opt{'fcn'},
171       $now,
172       $opt{'datacenter'},
173       ' ' x 1429 #filler
174     );
175   },
176   row => sub {
177     my ($cust_pay_batch, $pay_batch) = @_;
178     my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
179     $i++;
180     # The 1464 byte format supports up to 5 payments per line,
181     # but we're only going to send 1.
182     my $control = join('',
183       'D',                  # for 'debit'
184       sprintf("%09u", $i),  #record number
185       $opt{'origid'},
186       $opt{'fcn'},
187     );
188     my $payment = join('',
189       $opt{'cpacode'} || 437, # CPA code, defaults to "Internet access"
190       sprintf('%010.0f', $cust_pay_batch->amount*100),
191       $opt{'due'}, #due date...? XXX
192       sprintf('%09u', $aba),
193       sprintf('%-12s', $account),
194       ' ' x 22,
195       ' ' x 3,
196       $opt{'shortname'},
197       sprintf('%-30s', 
198         join(' ',
199           $cust_pay_batch->first, $cust_pay_batch->last)
200       ),
201       $opt{'longname'},
202       $opt{'origid'},
203       sprintf('%-19s', $cust_pay_batch->paybatchnum), # originator reference num
204       $opt{'retbranch'},
205       $opt{'retacct'}, 
206       ' ' x 22,
207       ' ' x 2,
208       '0' x 11,
209     );
210     return $control . $payment . (' ' x 720);
211   },
212   footer => sub {
213     my ($pay_batch, $batchcount, $batchtotal) = @_;
214     join('',
215       'Z',
216       sprintf('%09u', $batchcount + 2),
217       $opt{'origid'}, 
218       $opt{'fcn'},
219       sprintf('%014.0f', $batchtotal*100), # total of debit txns
220       sprintf('%08u', $batchcount), # number of debit txns
221       '0' x 14, # total of credit txns
222       '0' x 8, # total of credit txns
223       ' ' x 1396,
224     )
225   },
226 );
227
228 1;
229