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