RT# 81701 Add system log lines for EFT Canada upload/download
[freeside.git] / FS / bin / freeside-eftca-download
1 #!/usr/bin/perl
2
3 use strict;
4 use Getopt::Std;
5 use Date::Format qw(time2str);
6 use File::Temp qw(tempdir);
7 use Net::SFTP::Foreign;
8 use Expect;
9 use FS::UID qw(adminsuidsetup datasrc);
10 use FS::Record qw(qsearch qsearchs);
11 use FS::pay_batch;
12 use FS::cust_pay_batch;
13 use FS::Conf;
14 use FS::Log;
15
16 use vars qw( $opt_v $opt_a );
17 getopts('va:');
18
19 #$Net::SFTP::Foreign::debug = -1;
20 sub HELP_MESSAGE { "
21   Usage:
22       freeside-eftca-download [ -v ] [ -a archivedir ] user\n
23 " }
24
25 my @fields = (
26   'tid',          # transaction ID
27   'paybatchnum',  # reference field
28   'returncode',   # status code
29   'returndate',
30   'paid',         # dollars and cents, with decimal
31   'type',
32   'first',
33   'last',
34   'account',
35   'bank',
36   'transit',
37 );
38
39 my $user = shift or die &HELP_MESSAGE;
40 adminsuidsetup $user;
41
42 my $log = FS::Log->new('freeside-eftca-download');
43 log_info( "EFT Canada download started\n" );
44
45 if ( $opt_a ) {
46   log_error_and_die( "no such directory: $opt_a\n" )
47     unless -d $opt_a;
48   log_error_and_die(
49     "archive directory $opt_a is not writable by the freeside user\n"
50   ) unless -w $opt_a;
51 }
52
53 #my $tmpdir = File::Temp->newdir();
54 my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
55
56 my $conf = new FS::Conf;
57
58 my @agents;
59 if ( $conf->exists('batch-spoolagent') ) {
60   @agents = qsearch('agent', { 'disabled' => '' });
61 } else {
62   @agents = (1);
63 }
64
65 foreach my $agent (@agents) {
66
67   my @batchconf;
68   if ( $conf->exists('batch-spoolagent') ) {
69     @batchconf = $conf->config('batchconfig-eft_canada', $agent->agentnum, 1);
70     if ( !length($batchconf[0]) ) {
71       log_info(
72         "agent '".$agent->agent.
73         "' has no batchconfig-eft_canada setting; skipped.\n"
74       );
75       next;
76     }
77   } else {
78     @batchconf = $conf->config('batchconfig-eft_canada');
79   }
80   # user, password, transaction code, delay days
81   my $user = $batchconf[0]
82     or log_error_and_die( "no EFT Canada batch username configured\n" );
83   my $pass = $batchconf[1]
84     or log_error_and_die( "no EFT Canada batch password configured\n" );
85
86   my $host = 'ftp.eftcanada.com';
87   log_info( "Connecting to $user\@$host...\n" );
88
89   my $sftp = Net::SFTP::Foreign->new( host     => $host,
90                                       user     => $user,
91                                       password => $pass,
92                                       timeout  => 30,
93                                     );
94   log_error_and_die("failed to connect to '$user\@$host'\n(".$sftp->error.")\n")
95     if $sftp->error;
96
97   $sftp->setcwd('/Returns');
98
99   my $files = $sftp->ls('.', wanted => qr/\.txt$/, names_only => 1);
100   log_info_and_die( "Finished: No response files found\n" )
101     if !@$files;
102
103   FILE: foreach my $filename (@$files) {
104     log_info( "Retrieving $filename\n" );
105     $sftp->get("$filename", "$tmpdir/$filename");
106     if($sftp->error) {
107       log_info( "failed to download $filename\n" );
108       next FILE;
109     }
110
111     #move to server archive dir
112     $sftp->rename("$filename", "Archive/$filename");
113     if($sftp->error) {
114       log_info(  "failed to archive $filename on server\n" );
115     } # process it anyway though
116
117     #copy to local archive dir
118     if ( $opt_a ) {
119       log_info( "Copying $tmpdir/$filename to archive dir $opt_a\n" );
120       system 'cp', "$tmpdir/$filename", $opt_a;
121       log_info( "failed to copy $tmpdir/$filename to $opt_a: $@" )
122         if $@;
123     }
124
125     open my $fh, "<$tmpdir/$filename";
126     # Some duplication with FS::pay_batch::import_results, but we're really 
127     # doing something different here.
128     my $csv = new Text::CSV_XS ( { quote_char => undef, sep_char => '|' } );
129     my %hash;
130     while (my $line = <$fh>) {
131       next if $line =~ /^\s*$/;
132       $csv->parse($line) or do {
133         log_info( "can't parse $filename: ".$csv->error_input."\n" );
134         next FILE; #parsing errors = reading the wrong kind of file
135       };
136       @hash{@fields} = $csv->fields();
137       log_info( "voiding paybatchnum#$hash{paybatchnum}\n" );
138       my $cpb = qsearchs('cust_pay_batch', 
139                           { paybatchnum => $hash{'paybatchnum'} });
140       if ( !$cpb ) {
141         log_info(
142           "can't find paybatchnum #$hash{paybatchnum} ".
143           "($hash{first} $hash{last}, $hash{paid})\n"
144         );
145         next;
146       }
147       my $error = $cpb->decline("Returned payment ($hash{returncode})");
148       if ( $error ) {
149         log_info( "can't void paybatchnum #$hash{paybatchnum}: $error\n" );
150       }
151     }
152     close $fh;
153   }
154
155 }
156
157 log_info( "Finished!\n" );
158
159 sub log_info {
160   my $log_message = shift;
161   $log->info( $log_message );
162   print STDERR $log_message if $opt_v;
163 }
164
165 sub log_info_and_die {
166   my $log_message = shift;
167   $log->info( $log_message );
168   die $log_message;
169 }
170
171 sub log_error_and_die {
172   my $log_message = shift;
173   $log->error( $log_message );
174   die $log_message;
175 }
176
177 =head1 NAME
178
179 freeside-eftca-download - Retrieve payment batch responses from EFT Canada.
180
181 =head1 SYNOPSIS
182
183   freeside-eftca-download [ -v ] [ -a archivedir ] user
184
185 =head1 DESCRIPTION
186
187 Command line tool to download returned payment reports from the EFT Canada 
188 gateway and void the returned payments.  Uses the login and password from 
189 'batchconfig-eft_canada'.
190
191 -v: Be verbose.
192
193 -a directory: Archive response files in the provided directory.
194
195 user: freeside username
196
197 =head1 BUGS
198
199 You need to manually SFTP to ftp.eftcanada.com from the freeside account 
200 and accept their key before running this script.
201
202 =head1 SEE ALSO
203
204 L<FS::pay_batch>
205
206 =cut
207
208 1;
209