RT# 82988 - Fixed so only formats that can handle electronic refunds can download...
[freeside.git] / FS / FS / pay_batch / eft_canada.pm
1 package FS::pay_batch::eft_canada;
2
3 use strict;
4 use vars qw(@ISA %import_info %export_info $name);
5 use FS::Record 'qsearch';
6 use FS::Conf;
7 use FS::cust_pay_batch;
8 use DateTime;
9
10 my $conf;
11 my $origid;
12
13 $name = 'eft_canada';
14
15 %import_info = ( filetype  => 'NONE' ); # see FS/bin/freeside-eftca-download
16
17 my ($business_trans_code, $personal_trans_code, $trans_code);
18 my $req_date; # requested process date, in %D format
19
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
27 );
28 my %holiday = (
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
37           },
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
46           },
47   2018 => {  2 => { map {$_=>1} 19 }, #family day
48              3 => { map {$_=>1} 30 }, #good friday
49              4 => { map {$_=>1}  2 }, #easter monday
50              5 => { map {$_=>1} 21 }, #victoria day
51              8 => { map {$_=>1}  6 }, #First Monday of August Civic Holiday
52              9 => { map {$_=>1}  3 }, #labour day
53             10 => { map {$_=>1}  8 }, #thanksgiving
54           },
55   2019 => {  2 => { map {$_=>1} 18 }, #family day
56              4 => { map {$_=>1} 19 }, #good friday
57              4 => { map {$_=>1} 22 }, #easter monday
58              5 => { map {$_=>1} 20 }, #victoria day
59              8 => { map {$_=>1}  5 }, #First Monday of August Civic Holiday
60              9 => { map {$_=>1}  2 }, #labour day
61             10 => { map {$_=>1} 14 }, #thanksgiving
62           },
63 );
64
65 sub is_holiday {
66   my $dt = shift;
67   return 1 if exists( $holiday_yearly{$dt->month} )
68           and exists( $holiday_yearly{$dt->month}{$dt->day} );
69   return 1 if exists( $holiday{$dt->year} )
70           and exists( $holiday{$dt->year}{$dt->month} )
71           and exists( $holiday{$dt->year}{$dt->month}{$dt->day} );
72   return 0;
73 }
74
75 %export_info = (
76
77   init => sub {
78     my $conf = shift;
79     my $agentnum = shift;
80     my @config;
81     if ( $conf->exists('batch-spoolagent') ) {
82       @config = $conf->config('batchconfig-eft_canada', $agentnum);
83     } else {
84       @config = $conf->config('batchconfig-eft_canada');
85     }
86     # SFTP login, password, business and personal trans codes, delay time
87     ($business_trans_code) = $config[2];
88     ($personal_trans_code) = $config[3];
89
90     my ($process_date) = process_dates($conf, $agentnum);
91     $req_date = $process_date->strftime('%D');
92   },
93
94   delimiter => '', # avoid blank lines for header/footer
95
96   # EFT Upload Specification for .CSV Files, Rev. 2.0
97   # not a true CSV format--strings aren't quoted, so be careful
98   row => sub {
99     my ($cust_pay_batch, $pay_batch) = @_;
100     my @fields;
101     # company + empty or first + last
102     my $company = sprintf('%.64s', $cust_pay_batch->cust_main->company);
103     if ( $company ) {
104       push @fields, 'Business';
105       push @fields, $company, '';
106       $trans_code = $business_trans_code;
107     }
108     else {
109       push @fields, 'Personal';
110       push @fields, map { sprintf('%.64s', $_) } 
111         $cust_pay_batch->first, $cust_pay_batch->last;
112         $trans_code = $personal_trans_code;
113     }
114     my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
115     my($bankno, $branch);
116     if ( $aba =~ /^0(\d{3})(\d{5})$/ ) { # standard format for Canadian bank ID
117       ($bankno, $branch) = ( $1, $2 );
118     } elsif ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #how we store branches
119       ($branch, $bankno) = ( $1, $2 );
120     } else {
121       die "invalid branch/routing number '$aba'\n";
122     }
123     push @fields, sprintf('%05s', $branch),
124                   sprintf('%03s', $bankno),
125                   $account,
126                   sprintf('%.02f', $cust_pay_batch->amount);
127     # DB = debit
128     push @fields, 'DB', $trans_code, $req_date;
129     push @fields, $cust_pay_batch->paybatchnum; # reference
130     # strip illegal characters that might occur in customer name
131     s/[,|']//g foreach @fields; # better substitution for these?
132     return join(',', @fields) . "\n";
133   },
134
135 );
136
137 sub download_note { # is a class method
138   my $class = shift;
139   my $pay_batch = shift;
140   my $conf = FS::Conf->new;
141   my $agentnum = $pay_batch->agentnum;
142   my ($process_date, $upload_date) = process_dates($conf, $agentnum);
143   my $date_format = $conf->config('date_format') || '%D';
144   my $days_until_upload = $upload_date->delta_days(DateTime->now);
145
146   my $note = '';
147   if ( $days_until_upload->days == 0 ) {
148     $note = 'Upload this file before 11:00 AM today'. 
149             ' (' . $upload_date->strftime($date_format) . '). ';
150   } elsif ( $days_until_upload->days == 1 ) {
151     $note = 'Upload this file before 11:00 AM tomorrow'. 
152             ' (' . $upload_date->strftime($date_format) . '). ';
153   } else {
154     $note = 'Upload this file before 11:00 AM on '.
155       $upload_date->strftime($date_format) . '. ';
156   }
157   $note .= 'Payments will be processed on '.
158     $process_date->strftime($date_format) . '.';
159
160   $note;
161 }
162
163 sub process_dates { # returns both process and upload dates
164   my ($conf, $agentnum) = @_;
165   my @config;
166   if ( $conf->exists('batch-spoolagent') ) {
167     @config = $conf->config('batchconfig-eft_canada', $agentnum);
168   } else {
169     @config = $conf->config('batchconfig-eft_canada');
170   }
171   
172   my $process_delay = $config[4] || 1;
173
174   my $ut = DateTime->now; # the latest time we assume the user
175                           # could upload the file
176   $ut->truncate(to => 'day')->set_hour(10); # is 10 AM on whatever day
177   if ( $ut < DateTime->now ) {
178     # then we would submit the file today but it's already too late
179     $ut->add(days => 1);
180   }
181   while (    $ut->day_of_week == 6 # Saturday
182           or $ut->day_of_week == 7 # Sunday
183           or is_holiday($ut)
184         )
185   {
186     $ut->add(days => 1);
187   }
188   # $ut is now the latest time that the user can upload the file.
189
190   # that time, plus the process delay, is the _earliest_ process date we can
191   # request. if that's on a weekend or holiday, the process date has to be
192   # later.
193
194   my $pt = $ut->clone();
195   $pt->add(days => $process_delay);
196   while (    $pt->day_of_week == 6
197           or $pt->day_of_week == 7
198           or is_holiday($pt)
199         )
200   {
201     $pt->add(days => 1);
202   }
203
204   ($pt, $ut);
205 }
206
207 1;