Add -v switch (verbose) to freeside-void-payments
[freeside.git] / FS / bin / freeside-void-payments
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw( $user $cust_main @customers );
5 use Getopt::Std;
6 use FS::UID qw(adminsuidsetup);
7 use FS::Record qw(qsearchs);
8 use FS::Conf;
9 use FS::cust_main;
10 use FS::cust_pay;
11 use FS::cust_pay_void;
12 use Business::OnlinePayment; # For retrieving the void list only.
13 use Time::Local;
14 use Date::Parse 'str2time';
15 use Date::Format 'time2str';
16
17 my %opt;
18 getopts("r:f:ca:g:s:e:vn", \%opt);
19
20 $user = shift or die &usage;
21 &adminsuidsetup( $user );
22
23 # The -g and -a options need to override this.
24 my $method = $opt{'c'} ? 'ECHECK' : 'CARD';
25 my $gateway;
26 if($opt{'g'}) {
27   $gateway = FS::payment_gateway->by_key($opt{'g'})
28     or die "Payment gateway not found: '".$opt{'g'}."'.";
29 }
30 elsif($opt{'a'}) {
31   my $agent = FS::agent->by_key($opt{'a'})
32     or die "Agent not found: '".$opt{'a'}."'.";
33   $gateway = $agent->payment_gateway(method => $method)
34     or die "Agent has no payment gateway for method '$method'.";
35 }
36
37 my ($processor, $login, $password, $action, @bop_options) =
38   FS::cust_main->default_payment_gateway($method);
39 my $gatewaynum = '';
40
41 if($gateway) {
42 # override the default gateway
43   $gatewaynum = $gateway->gatewaynum . '-' if $gateway->gatewaynum;
44   $processor = $gateway->gateway_module;
45   $login     = $gateway->gateway_username;
46   $password  = $gateway->gateway_password;
47   $action    = $gateway->gateway_action;
48   @bop_options = $gateway->options;
49 }
50
51 my @auths;
52 if($opt{'f'}) {
53 # Read the list of authorization numbers from a file.
54   my $in;
55   open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
56   @auths = grep /^\d+$/, <$in>;
57   chomp @auths;
58 }
59 else {
60 # Get the list from the processor.  This requires the processor module to 
61 # support get_returns.
62   my $transaction = new Business::OnlinePayment ( $processor, @bop_options );
63   if(! $transaction->can('get_returns')) {
64     die "'$processor' does not provide an automated void list.";
65   }
66   my @local = localtime;
67 # Start and end dates for this can be set via -s and -e.  If they're not,
68 # end defaults to midnight today and start defaults to one day before end.
69   my $end = defined($opt{'e'}) ? 
70       str2time($opt{'e'}) : timelocal(0, 0, 0, @local[3,4,5]);
71   my $start = defined($opt{'s'}) ?
72       str2time($opt{'s'}) : $end - 86400;
73   die "Invalid date range: '$start'-'$end'" if not ($start and $end);
74   $transaction->content (
75     login     => $login,
76     password  => $password,
77     start     => time2str("%Y-%m-%d",$start),
78     end       => time2str("%Y-%m-%d",$end),
79     );
80   @auths = $transaction->get_returns;
81 }
82
83 $opt{'r'} ||= 'freeside-void-payments';
84 my $success = 0;
85 my $notfound = 0;
86 print "Voiding ".scalar(@auths)." transactions:\n" if $opt{'v'};
87 foreach my $authnum (@auths) {
88   my $paybatch = $gatewaynum . $processor . ':' . $authnum;
89   my $cust_pay = qsearchs('cust_pay', { paybatch => $paybatch } );
90   my $error;
91   if($cust_pay) {
92     $error = $cust_pay->void($opt{'r'});
93     $success++ if not $error;
94   }
95   else {
96     my $cpv = qsearchs('cust_pay_void', { paybatch => $paybatch });
97     if($cpv) {
98       $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) . 
99         ' by ' . $cpv->otaker;
100     }
101     else {
102       $error = 'not found';
103       $notfound++;
104     }
105   }
106   if($opt{'v'}) {
107     print $authnum;
108     print "\t($error)" if $error; 
109     print "\n";
110   }
111 }
112
113 if($opt{'v'}) {
114   print scalar(@auths)." transactions: $success voided, $notfound not found\n";
115 }
116
117 sub usage {
118     die "Usage:\n\n  freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] [ -v ] [ -n ]user\n";
119 }
120
121 __END__
122
123 # Documentation
124
125 =head1 NAME
126
127 freeside-void-payments - Automatically void a list of returned payments.
128
129 =head1 SYNOPSIS
130
131   freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] [ -v ] [ -n ] user
132
133 =head1 DESCRIPTION
134
135 Voids payments that were returned by the payment processor.  Can be 
136 run periodically from crontab or manually after receiving a list of 
137 returned payments.  Normally this is a meaningful operation only for 
138 electronic checks.
139
140 This script voids payments based on the combination of gateway (see 
141 L<FS::payment_gateway>) and authorization number, since this is 
142 generally how the processor will identify them later.
143
144   -f: Read the list of authorization numbers from the specified file.  
145       If they are not from the default payment gateway, -g or -a 
146       must be given to identify the gateway.
147       
148   If -f is not given, the script will attempt to contact the gateway 
149   and download a list of returned transactions.  To support this, 
150   the Business::OnlinePayment module for the processor must implement 
151   the I<get_returns()> method.  For an example, see 
152   L<Business::OnlinePayment::WesternACH>.
153
154   -s, -e: Specify the starting and ending dates for the void list.  
155       This has no effect if -f is given.  The end date defaults to 
156       today, and the start date defaults to one day before the end date.
157
158   -r: The reason for voiding the payments, to be stored in the database.
159
160   -g: The L<FS::payment_gateway> number for the gateway that handled 
161       these payments.  If -f is not given, this determines which 
162       gateway will be contacted.  This overrides -a.
163
164   -a: The agentnum whose default gateway will be used.  If neither -a 
165       nor -g is given, the system default gateway will be used.
166
167   -c: Use the default gateway for check transactions rather than 
168       credit cards.
169
170   -v: Be verbose.
171
172 A warning will be emitted for each transaction that can't be found.  
173 This may happen if it's already been voided, or if the gateway 
174 doesn't match.
175
176 =head1 EXAMPLE
177
178 Given 'returns.txt', which contains one authorization number on each 
179 line, provided by your default e-check processor:
180
181   freeside-void-payments -f returns.txt -c -r 'Returned check'
182
183 If your default processor is Western ACH, which supports automated 
184 returns processing, this voids all returned payments since 2009-06-01:
185
186   freeside-void-payments -r 'Returned check' -s 2009-06-01
187
188 This, in your crontab, will void returned payments for the last 
189 day at 8:30 every morning:
190
191   30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'
192
193 =head1 BUGS
194
195 Most payment gateways don't support it, making the script largely useless.
196
197 =head1 SEE ALSO
198
199 L<Business::OnlinePayment>, L<FS::cust_pay>
200
201 =cut