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