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:vn", \%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 my ($processor, $login, $password, $action, @bop_options) =
38 FS::cust_main->default_payment_gateway($method);
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;
53 # Read the list of authorization numbers from a file.
55 open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
56 @auths = grep /^\d+$/, <$in>;
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.";
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 (
76 password => $password,
77 start => time2str("%Y-%m-%d",$start),
78 end => time2str("%Y-%m-%d",$end),
80 @auths = $transaction->get_returns;
83 $opt{'r'} ||= 'freeside-void-payments';
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 } );
92 $error = $cust_pay->void($opt{'r'});
93 $success++ if not $error;
96 my $cpv = qsearchs('cust_pay_void', { paybatch => $paybatch });
98 $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) .
99 ' by ' . $cpv->otaker;
102 $error = 'not found';
108 print "\t($error)" if $error;
114 print scalar(@auths)." transactions: $success voided, $notfound not found\n";
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";
127 freeside-void-payments - Automatically void a list of returned payments.
131 freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] [ -v ] [ -n ] user
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
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.
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.
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>.
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.
158 -r: The reason for voiding the payments, to be stored in the database.
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.
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.
167 -c: Use the default gateway for check transactions rather than
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
178 Given 'returns.txt', which contains one authorization number on each
179 line, provided by your default e-check processor:
181 freeside-void-payments -f returns.txt -c -r 'Returned check'
183 If your default processor is Western ACH, which supports automated
184 returns processing, this voids all returned payments since 2009-06-01:
186 freeside-void-payments -r 'Returned check' -s 2009-06-01
188 This, in your crontab, will void returned payments for the last
189 day at 8:30 every morning:
191 30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'
195 Most payment gateways don't support it, making the script largely useless.
199 L<Business::OnlinePayment>, L<FS::cust_pay>