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