1 package FS::pay_batch::eft_canada;
4 use vars qw(@ISA %import_info %export_info $name);
5 use FS::Record 'qsearch';
7 use FS::cust_pay_batch;
15 %import_info = ( filetype => 'NONE' ); # see FS/bin/freeside-eftca-download
17 my ($business_trans_code, $personal_trans_code, $trans_code);
18 my $req_date; # requested process date, in %D format
20 # use Date::Holidays::CA for this?
21 #ref http://gocanada.about.com/od/canadatravelplanner/a/canada_holidays.htm
22 my %holiday_yearly = (
23 1 => { map {$_=>1} 1 }, #new year's
24 7 => { map {$_=>1} 1 }, #canada day
25 11 => { map {$_=>1} 11 }, #remembrance day
26 12 => { map {$_=>1} 25 }, #christmas
27 12 => { map {$_=>1} 26 }, #boxing day
30 2016 => { 2 => { map {$_=>1} 15 }, #family day
31 3 => { map {$_=>1} 25 }, #good friday
32 3 => { map {$_=>1} 28 }, #easter monday
33 5 => { map {$_=>1} 23 }, #victoria day
34 8 => { map {$_=>1} 1 }, #First Monday of August Civic Holiday
35 9 => { map {$_=>1} 5 }, #labour day
36 10 => { map {$_=>1} 10 }, #thanksgiving
38 2017 => { 2 => { map {$_=>1} 20 }, #family day
39 4 => { map {$_=>1} 14 }, #good friday
40 4 => { map {$_=>1} 17 }, #easter monday
41 5 => { map {$_=>1} 22 }, #victoria day
42 8 => { map {$_=>1} 7 }, #First Monday of August Civic Holiday
43 9 => { map {$_=>1} 4 }, #labour day
44 10 => { map {$_=>1} 9 }, #thanksgiving
46 2018 => { 2 => { map {$_=>1} 19 }, #family day
47 3 => { map {$_=>1} 30 }, #good friday
48 4 => { map {$_=>1} 2 }, #easter monday
49 5 => { map {$_=>1} 21 }, #victoria day
50 8 => { map {$_=>1} 6 }, #First Monday of August Civic Holiday
51 9 => { map {$_=>1} 3 }, #labour day
52 10 => { map {$_=>1} 8 }, #thanksgiving
58 return 1 if exists( $holiday_yearly{$dt->month} )
59 and exists( $holiday_yearly{$dt->month}{$dt->day} );
60 return 1 if exists( $holiday{$dt->year} )
61 and exists( $holiday{$dt->year}{$dt->month} )
62 and exists( $holiday{$dt->year}{$dt->month}{$dt->day} );
72 if ( $conf->exists('batch-spoolagent') ) {
73 @config = $conf->config('batchconfig-eft_canada', $agentnum);
75 @config = $conf->config('batchconfig-eft_canada');
77 # SFTP login, password, business and personal trans codes, delay time
78 ($business_trans_code) = $config[2];
79 ($personal_trans_code) = $config[3];
81 my ($process_date) = process_dates($conf, $agentnum);
82 $req_date = $process_date->strftime('%D');
85 delimiter => '', # avoid blank lines for header/footer
87 # EFT Upload Specification for .CSV Files, Rev. 2.0
88 # not a true CSV format--strings aren't quoted, so be careful
90 my ($cust_pay_batch, $pay_batch) = @_;
92 # company + empty or first + last
93 my $company = sprintf('%.64s', $cust_pay_batch->cust_main->company);
95 push @fields, 'Business';
96 push @fields, $company, '';
97 $trans_code = $business_trans_code;
100 push @fields, 'Personal';
101 push @fields, map { sprintf('%.64s', $_) }
102 $cust_pay_batch->first, $cust_pay_batch->last;
103 $trans_code = $personal_trans_code;
105 my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
106 my($bankno, $branch);
107 if ( $aba =~ /^0(\d{3})(\d{5})$/ ) { # standard format for Canadian bank ID
108 ($bankno, $branch) = ( $1, $2 );
109 } elsif ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #how we store branches
110 ($branch, $bankno) = ( $1, $2 );
112 die "invalid branch/routing number '$aba'\n";
114 push @fields, sprintf('%05s', $branch),
115 sprintf('%03s', $bankno),
117 sprintf('%.02f', $cust_pay_batch->amount);
119 push @fields, 'DB', $trans_code, $req_date;
120 push @fields, $cust_pay_batch->paybatchnum; # reference
121 # strip illegal characters that might occur in customer name
122 s/[,|']//g foreach @fields; # better substitution for these?
123 return join(',', @fields) . "\n";
128 sub download_note { # is a class method
130 my $pay_batch = shift;
131 my $conf = FS::Conf->new;
132 my $agentnum = $pay_batch->agentnum;
133 my ($process_date, $upload_date) = process_dates($conf, $agentnum);
134 my $date_format = $conf->config('date_format') || '%D';
135 my $days_until_upload = $upload_date->delta_days(DateTime->now);
138 if ( $days_until_upload->days == 0 ) {
139 $note = 'Upload this file before 11:00 AM today'.
140 ' (' . $upload_date->strftime($date_format) . '). ';
141 } elsif ( $days_until_upload->days == 1 ) {
142 $note = 'Upload this file before 11:00 AM tomorrow'.
143 ' (' . $upload_date->strftime($date_format) . '). ';
145 $note = 'Upload this file before 11:00 AM on '.
146 $upload_date->strftime($date_format) . '. ';
148 $note .= 'Payments will be processed on '.
149 $process_date->strftime($date_format) . '.';
154 sub process_dates { # returns both process and upload dates
155 my ($conf, $agentnum) = @_;
157 if ( $conf->exists('batch-spoolagent') ) {
158 @config = $conf->config('batchconfig-eft_canada', $agentnum);
160 @config = $conf->config('batchconfig-eft_canada');
163 my $process_delay = $config[4] || 1;
165 my $ut = DateTime->now; # the latest time we assume the user
166 # could upload the file
167 $ut->truncate(to => 'day')->set_hour(10); # is 10 AM on whatever day
168 if ( $ut < DateTime->now ) {
169 # then we would submit the file today but it's already too late
172 while ( $ut->day_of_week == 6 # Saturday
173 or $ut->day_of_week == 7 # Sunday
179 # $ut is now the latest time that the user can upload the file.
181 # that time, plus the process delay, is the _earliest_ process date we can
182 # request. if that's on a weekend or holiday, the process date has to be
185 my $pt = $ut->clone();
186 $pt->add(days => $process_delay);
187 while ( $pt->day_of_week == 6
188 or $pt->day_of_week == 7