4 use vars qw( $user $cust_main @customers );
6 use FS::UID qw(adminsuidsetup);
7 use FS::Record qw(qsearchs);
11 use FS::cust_pay_void;
12 use Business::OnlinePayment; # For retrieving the void list only.
14 use Date::Parse 'str2time';
15 use Date::Format 'time2str';
18 getopts("r:f:ca:g:s:e:vnX:", \%opt);
20 $user = shift or die &usage;
21 &adminsuidsetup( $user );
23 # The -g and -a options need to override this.
24 my $method = $opt{'c'} ? 'ECHECK' : 'CARD';
27 $gateway = FS::payment_gateway->by_key($opt{'g'})
28 or die "Payment gateway not found: '".$opt{'g'}."'.";
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'.";
37 if(defined($opt{'X'})) {
38 die "Cancellation reason not found: '".$opt{'X'}."'"
39 if(! qsearchs('reason', { reasonnum => $opt{'X'} } ) );
42 my ($processor, $login, $password, $action, @bop_options) =
43 FS::cust_main->default_payment_gateway($method);
47 # override the default gateway
48 $gatewaynum = $gateway->gatewaynum . '-' if $gateway->gatewaynum;
49 $processor = $gateway->gateway_module;
50 $login = $gateway->gateway_username;
51 $password = $gateway->gateway_password;
52 $action = $gateway->gateway_action;
53 @bop_options = $gateway->options;
58 # Read the list of authorization numbers from a file.
60 open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
61 @auths = grep /^\d+$/, <$in>;
65 # Get the list from the processor. This requires the processor module to
66 # support get_returns.
67 my $transaction = new Business::OnlinePayment ( $processor, @bop_options );
68 if(! $transaction->can('get_returns')) {
69 die "'$processor' does not provide an automated void list.";
71 my @local = localtime;
72 # Start and end dates for this can be set via -s and -e. If they're not,
73 # end defaults to midnight today and start defaults to one day before end.
74 my $end = defined($opt{'e'}) ?
75 str2time($opt{'e'}) : timelocal(0, 0, 0, @local[3,4,5]);
76 my $start = defined($opt{'s'}) ?
77 str2time($opt{'s'}) : $end - 86400;
78 die "Invalid date range: '$start'-'$end'" if not ($start and $end);
79 $transaction->content (
81 password => $password,
82 start => time2str("%Y-%m-%d",$start),
83 end => time2str("%Y-%m-%d",$end),
85 @auths = $transaction->get_returns;
88 $opt{'r'} ||= 'freeside-void-payments';
92 print "Voiding ".scalar(@auths)." transactions:\n" if $opt{'v'};
93 foreach my $authnum (@auths) {
94 my $paybatch = $gatewaynum . $processor . ':' . $authnum;
95 my $cust_pay = qsearchs('cust_pay', { paybatch => $paybatch } );
99 $error = $cust_pay->void($opt{'r'});
100 $success++ if not $error;
101 if($opt{'X'} and not $error) {
102 $cancel_error = join(';',$cust_pay->cust_main->cancel('reason' => $opt{'X'}));
103 $canceled++ if !$cancel_error;
107 my $cpv = qsearchs('cust_pay_void', { paybatch => $paybatch });
109 $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) .
110 ' by ' . $cpv->otaker;
113 $error = 'not found';
123 print "\t(canceled service)" if !$cancel_error;
124 print "\n\t(cancellation failed: $cancel_error)" if $cancel_error;
131 print scalar(@auths)." transactions: $success voided, $notfound not found\n";
132 print "$canceled customer".($canceled == 1 ? '' : 's')." canceled\n" if $opt{'X'};
136 die "Usage:\n\n freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] [ -v ] [ -n ] [-X reasonnum ] user\n";
145 freeside-void-payments - Automatically void a list of returned payments.
149 freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] [ -v ] [ -n ] user
153 Voids payments that were returned by the payment processor. Can be
154 run periodically from crontab or manually after receiving a list of
155 returned payments. Normally this is a meaningful operation only for
158 This script voids payments based on the combination of gateway (see
159 L<FS::payment_gateway>) and authorization number, since this is
160 generally how the processor will identify them later.
162 -f: Read the list of authorization numbers from the specified file.
163 If they are not from the default payment gateway, -g or -a
164 must be given to identify the gateway.
166 If -f is not given, the script will attempt to contact the gateway
167 and download a list of returned transactions. To support this,
168 the Business::OnlinePayment module for the processor must implement
169 the I<get_returns()> method. For an example, see
170 L<Business::OnlinePayment::WesternACH>.
172 -s, -e: Specify the starting and ending dates for the void list.
173 This has no effect if -f is given. The end date defaults to
174 today, and the start date defaults to one day before the end date.
176 -r: The reason for voiding the payments, to be stored in the database.
178 -g: The L<FS::payment_gateway> number for the gateway that handled
179 these payments. If -f is not given, this determines which
180 gateway will be contacted. This overrides -a.
182 -a: The agentnum whose default gateway will be used. If neither -a
183 nor -g is given, the system default gateway will be used.
185 -c: Use the default gateway for check transactions rather than
190 -X: Automatically cancel all packages belonging to customers whose payments
191 were returned. Requires a cancellation reasonnum (from L<FS::reason>).
193 A warning will be emitted for each transaction that can't be found.
194 This may happen if it's already been voided, or if the gateway
199 Given 'returns.txt', which contains one authorization number on each
200 line, provided by your default e-check processor:
202 freeside-void-payments -f returns.txt -c -r 'Returned check'
204 If your default processor is Western ACH, which supports automated
205 returns processing, this voids all returned payments since 2009-06-01:
207 freeside-void-payments -r 'Returned check' -s 2009-06-01
209 This, in your crontab, will void returned payments for the last
210 day at 8:30 every morning:
212 30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'
216 Most payment gateways don't support it, making the script largely useless.
220 L<Business::OnlinePayment>, L<FS::cust_pay>