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 11 => { map {$_=>1} 11 }, #remembrance day
25 12 => { map {$_=>1} 25 }, #christmas
26 12 => { map {$_=>1} 26 }, #boxing day
29 2016 => { 2 => { map {$_=>1} 15 }, #family day
30 3 => { map {$_=>1} 25 }, #good friday
31 3 => { map {$_=>1} 28 }, #easter monday
32 5 => { map {$_=>1} 23 }, #victoria day
33 7 => { map {$_=>1} 1 }, #canada 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 7 => { map {$_=>1} 1 }, #canada day
43 8 => { map {$_=>1} 7 }, #First Monday of August Civic Holiday
44 9 => { map {$_=>1} 4 }, #labour day
45 10 => { map {$_=>1} 9 }, #thanksgiving
51 return 1 if exists( $holiday_yearly{$dt->month} )
52 and exists( $holiday_yearly{$dt->month}{$dt->day} );
53 return 1 if exists( $holiday{$dt->year} )
54 and exists( $holiday{$dt->year}{$dt->month} )
55 and exists( $holiday{$dt->year}{$dt->month}{$dt->day} );
65 if ( $conf->exists('batch-spoolagent') ) {
66 @config = $conf->config('batchconfig-eft_canada', $agentnum);
68 @config = $conf->config('batchconfig-eft_canada');
70 # SFTP login, password, business and personal trans codes, delay time
71 ($business_trans_code) = $config[2];
72 ($personal_trans_code) = $config[3];
74 my ($process_date) = process_dates($conf, $agentnum);
75 $req_date = $process_date->strftime('%D');
78 delimiter => '', # avoid blank lines for header/footer
80 # EFT Upload Specification for .CSV Files, Rev. 2.0
81 # not a true CSV format--strings aren't quoted, so be careful
83 my ($cust_pay_batch, $pay_batch) = @_;
85 # company + empty or first + last
86 my $company = sprintf('%.64s', $cust_pay_batch->cust_main->company);
88 push @fields, 'Business';
89 push @fields, $company, '';
90 $trans_code = $business_trans_code;
93 push @fields, 'Personal';
94 push @fields, map { sprintf('%.64s', $_) }
95 $cust_pay_batch->first, $cust_pay_batch->last;
96 $trans_code = $personal_trans_code;
98 my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
100 if ( $aba =~ /^0(\d{3})(\d{5})$/ ) { # standard format for Canadian bank ID
101 ($bankno, $branch) = ( $1, $2 );
102 } elsif ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #how we store branches
103 ($branch, $bankno) = ( $1, $2 );
105 die "invalid branch/routing number '$aba'\n";
107 push @fields, sprintf('%05s', $branch),
108 sprintf('%03s', $bankno),
110 sprintf('%.02f', $cust_pay_batch->amount);
112 push @fields, 'DB', $trans_code, $req_date;
113 push @fields, $cust_pay_batch->paybatchnum; # reference
114 # strip illegal characters that might occur in customer name
115 s/[,|']//g foreach @fields; # better substitution for these?
116 return join(',', @fields) . "\n";
121 sub download_note { # is a class method
123 my $pay_batch = shift;
124 my $conf = FS::Conf->new;
125 my $agentnum = $pay_batch->agentnum;
126 my ($process_date, $upload_date) = process_dates($conf, $agentnum);
127 my $date_format = $conf->config('date_format') || '%D';
128 my $days_until_upload = $upload_date->delta_days(DateTime->now);
131 if ( $days_until_upload->days == 0 ) {
132 $note = 'Upload this file before 11:00 AM today'.
133 ' (' . $upload_date->strftime($date_format) . '). ';
134 } elsif ( $days_until_upload->days == 1 ) {
135 $note = 'Upload this file before 11:00 AM tomorrow'.
136 ' (' . $upload_date->strftime($date_format) . '). ';
138 $note = 'Upload this file before 11:00 AM on '.
139 $upload_date->strftime($date_format) . '. ';
141 $note .= 'Payments will be processed on '.
142 $process_date->strftime($date_format) . '.';
147 sub process_dates { # returns both process and upload dates
148 my ($conf, $agentnum) = @_;
150 if ( $conf->exists('batch-spoolagent') ) {
151 @config = $conf->config('batchconfig-eft_canada', $agentnum);
153 @config = $conf->config('batchconfig-eft_canada');
156 my $process_delay = $config[4] || 1;
158 my $ut = DateTime->now; # the latest time we assume the user
159 # could upload the file
160 $ut->truncate(to => 'day')->set_hour(10); # is 10 AM on whatever day
161 if ( $ut < DateTime->now ) {
162 # then we would submit the file today but it's already too late
165 while ( $ut->day_of_week == 6 # Saturday
166 or $ut->day_of_week == 7 # Sunday
172 # $ut is now the latest time that the user can upload the file.
174 # that time, plus the process delay, is the _earliest_ process date we can
175 # request. if that's on a weekend or holiday, the process date has to be
178 my $pt = $ut->clone();
179 $pt->add(days => $process_delay);
180 while ( $pt->day_of_week == 6
181 or $pt->day_of_week == 7