adjustments to ipifony download script, #18333
[freeside.git] / FS / bin / freeside-ipifony-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 FS::UID qw(adminsuidsetup);
9 use FS::Record qw(qsearch qsearchs);
10 use FS::cust_main;
11 use FS::Conf;
12 use Text::CSV;
13
14 my %opt;
15 getopts('va:P:', \%opt);
16
17 #$Net::SFTP::Foreign::debug = -1;
18 sub HELP_MESSAGE { '
19   Usage:
20       freeside-ipifony-download 
21         [ -v ]
22         [ -a archivedir ]
23         [ -P port ]
24         freesideuser sftpuser@hostname[:path]
25 ' }
26
27 my @fields = (
28   'custnum',
29   'date_desc',
30   'quantity',
31   'amount',
32   'classname',
33 );
34
35 my $user = shift or die &HELP_MESSAGE;
36 adminsuidsetup $user;
37
38 # for statistics
39 my $num_charges = 0;
40 my $num_errors = 0;
41 my $sum_charges = 0;
42 # cache classnums
43 my %classnum_of;
44
45 if ( $opt{a} ) {
46   die "no such directory: $opt{a}\n"
47     unless -d $opt{a};
48   die "archive directory $opt{a} is not writable by the freeside user\n"
49     unless -w $opt{a};
50 }
51
52 #my $tmpdir = File::Temp->newdir();
53 my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
54
55 my $host = shift
56   or die &HELP_MESSAGE;
57 my ($sftpuser, $path);
58 $host =~ s/^(.+)\@//;
59 $sftpuser = $1 || $ENV{USER};
60 $host =~ s/:(.*)//;
61 $path = $1;
62
63 my $port = 22;
64 if ( $opt{P} =~ /^(\d+)$/ ) {
65   $port = $1;
66 }
67
68 # for now assume SFTP download as the only method
69 print STDERR "Connecting to $sftpuser\@$host...\n" if $opt{v};
70
71 my $sftp = Net::SFTP::Foreign->new(
72   host      => $host,
73   user      => $sftpuser,
74   port      => $port,
75   # for now we don't support passwords. use authorized_keys.
76   timeout   => 30,
77   more      => ($opt{v} ? '-v' : ''),
78 );
79 die "failed to connect to '$sftpuser\@$host'\n(".$sftp->error.")\n"
80   if $sftp->error;
81
82 $sftp->setcwd($path) if $path;
83
84 my $files = $sftp->ls('.', wanted => qr/\.csv$/, names_only => 1);
85 if (!@$files) {
86   print STDERR "No charge files found.\n" if $opt{v};
87   exit(-1);
88 }
89 FILE: foreach my $filename (@$files) {
90   print STDERR "Retrieving $filename\n" if $opt{v};
91   $sftp->get("$filename", "$tmpdir/$filename");
92   if($sftp->error) {
93     warn "failed to download $filename\n";
94     next FILE;
95   }
96
97   # make sure server archive dir exists
98   if ( !$sftp->stat('Archive') ) {
99     print STDERR "Creating $path/Archive\n" if $opt{v};
100     $sftp->mkdir('Archive');
101     if($sftp->error) {
102       # something is seriously wrong
103       die "failed to create archive directory on server:\n".$sftp->error."\n";
104     }
105   }
106   #move to server archive dir
107   $sftp->rename("$filename", "Archive/$filename");
108   if($sftp->error) {
109     warn "failed to archive $filename on server:\n".$sftp->error."\n";
110   } # process it anyway, I guess/
111
112   #copy to local archive dir
113   if ( $opt{a} ) {
114     print STDERR "Copying $tmpdir/$filename to archive dir $opt{a}\n"
115       if $opt{v};
116     copy("$tmpdir/$filename", $opt{a});
117     warn "failed to copy $tmpdir/$filename to $opt{a}: $!" if $!;
118   }
119
120   open my $fh, "<$tmpdir/$filename";
121   my $header = <$fh>;
122   if ($header !~ /^cust_id/) {
123     warn "warning: $filename has incorrect header row:\n$header\n";
124     # but try anyway
125   }
126   my $csv = Text::CSV->new; # orthodox CSV
127   my %hash;
128   while (my $line = <$fh>) {
129     $csv->parse($line) or do {
130       warn "can't parse $filename: ".$csv->error_input."\n";
131       next FILE;
132     };
133     @hash{@fields} = $csv->fields();
134     my $cust_main = FS::cust_main->by_key($hash{custnum});
135     if (!$cust_main) {
136       warn "customer #$hash{custnum} not found\n";
137       next;
138     }
139     print STDERR "Found customer #$hash{custnum}: ".$cust_main->name."\n"
140       if $opt{v};
141
142     # construct arguments for $cust_main->charge
143     my %opt = (
144       amount      => $hash{amount},
145       quantity    => $hash{quantity},
146       start_date  => $cust_main->next_bill_date,
147       pkg         => $hash{date_desc},
148     );
149     if (my $classname = $hash{classname}) {
150       if (!exists($classnum_of{$classname}) ) {
151         # then look it up
152         my $pkg_class = qsearch('pkg_class', { classname => $classname });
153         $classnum_of{$classname} = $pkg_class ? $pkg_class->classnum : '';
154       }
155       $opt{classnum} = $classnum_of{$classname};
156     }
157     # XXX what's the tax status of these charges?
158     print STDERR "  Charging $hash{amount}\n"
159       if $opt{v};
160     my $error = $cust_main->charge(\%opt);
161     if ($error) {
162       warn "Error creating charge: $error" if $error;
163       $num_errors++;
164     } else {
165       $num_charges++;
166       $sum_charges += $hash{amount};
167     }
168   } #while $line
169   close $fh;
170 } #FILE
171
172 if ($opt{v}) {
173   print STDERR "
174 Finished!
175   Processed files: @$files
176   Created charges: $num_charges
177   Sum of charges: \$".sprintf('%0.2f', $sum_charges)."
178   Errors: $num_errors
179 ";
180 }
181
182 =head1 NAME
183
184 freeside-eftca-download - Retrieve payment batch responses from EFT Canada.
185
186 =head1 SYNOPSIS
187
188   freeside-eftca-download [ -v ] [ -a archivedir ] user
189
190 =head1 DESCRIPTION
191
192 Command line tool to download returned payment reports from the EFT Canada 
193 gateway and void the returned payments.  Uses the login and password from 
194 'batchconfig-eft_canada'.
195
196 -v: Be verbose.
197
198 -a directory: Archive response files in the provided directory.
199
200 user: freeside username
201
202 =head1 BUGS
203
204 You need to manually SFTP to ftp.eftcanada.com from the freeside account 
205 and accept their key before running this script.
206
207 =head1 SEE ALSO
208
209 L<FS::pay_batch>
210
211 =cut
212
213 1;
214