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'}) and !qsearchs('reason', { reasonnum => opt{'X'} })) {
38 die "Cancellation reason not found: '".$opt{'X'}."'";
41 my ($processor, $login, $password, $action, @bop_options) =
42 FS::cust_main->default_payment_gateway($method);
46 # override the default gateway
47 $gatewaynum = $gateway->gatewaynum . '-' if $gateway->gatewaynum;
48 $processor = $gateway->gateway_module;
49 $login = $gateway->gateway_username;
50 $password = $gateway->gateway_password;
51 $action = $gateway->gateway_action;
52 @bop_options = $gateway->options;
57 # Read the list of authorization numbers from a file.
59 open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
60 @auths = grep /^\d+$/, <$in>;
64 # Get the list from the processor. This requires the processor module to
65 # support get_returns.
66 my $transaction = new Business::OnlinePayment ( $processor, @bop_options );
67 if(! $transaction->can('get_returns')) {
68 die "'$processor' does not provide an automated void list.";
70 my @local = localtime;
71 # Start and end dates for this can be set via -s and -e. If they're not,
72 # end defaults to midnight today and start defaults to one day before end.
73 my $end = defined($opt{'e'}) ?
74 str2time($opt{'e'}) : timelocal(0, 0, 0, @local[3,4,5]);
75 my $start = defined($opt{'s'}) ?
76 str2time($opt{'s'}) : $end - 86400;
77 die "Invalid date range: '$start'-'$end'" if not ($start and $end);
78 $transaction->content (
80 password => $password,
81 start => time2str("%Y-%m-%d",$start),
82 end => time2str("%Y-%m-%d",$end),
84 @auths = $transaction->get_returns;
87 $opt{'r'} ||= 'freeside-void-payments';
91 print "Voiding ".scalar(@auths)." transactions:\n" if $opt{'v'};
92 foreach my $authnum (@auths) {
93 my $cust_pay = qsearchs('cust_pay', {
94 gatewaynum => $gatewaynum,
95 processor => $processor,
96 authorization => $authnum,
101 $error = $cust_pay->void($opt{'r'});
102 $success++ if not $error;
103 if($opt{'X'} and not $error) {
104 $cancel_error = join(';',$cust_pay->cust_main->cancel('reason' => $opt{'X'}));
105 $canceled++ if !$cancel_error;
109 my $cpv = qsearchs('cust_pay_void', {
110 gatewaynum => $gatewaynum,
111 processor => $processor,
112 authorization => $authnum,
115 $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) .
116 ' by ' . $cpv->otaker;
119 $error = 'not found';
129 print "\t(canceled service)" if !$cancel_error;
130 print "\n\t(cancellation failed: $cancel_error)" if $cancel_error;
137 print scalar(@auths)." transactions: $success voided, $notfound not found\n";
138 print "$canceled customer".($canceled == 1 ? '' : 's')." canceled\n" if $opt{'X'};
142 die "Usage:\n\n freeside-void-payments [ options ] user
145 -a agentnum use agentnum's gateway information
146 -g gatewaynum use gatewaynum
147 -f file read transaction numbers from file
148 -c use ECHECK gateway instead of CARD
149 -r reason specify void reason (as a string)
152 -e end-date limit by payment return date
153 -X reasonnum cancel customers whose payments are voided
154 (specify cancellation reason number)
165 freeside-void-payments - Automatically void a list of returned payments.
169 freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ]
171 [ -g gatewaynum | -a agentnum ]
180 Voids payments that were returned by the payment processor. Can be
181 run periodically from crontab or manually after receiving a list of
182 returned payments. Normally this is a meaningful operation only for
185 This script voids payments based on the combination of gateway (see
186 L<FS::payment_gateway>) and authorization number, since this is
187 generally how the processor will identify them later.
189 -f: Read the list of authorization numbers from the specified file.
190 If they are not from the default payment gateway, -g or -a
191 must be given to identify the gateway.
193 If -f is not given, the script will attempt to contact the gateway
194 and download a list of returned transactions. To support this,
195 the Business::OnlinePayment module for the processor must implement
196 the get_returns() method. For an example, see
197 Business::OnlinePayment::WesternACH.
199 -s, -e: Specify the starting and ending dates for the void list.
200 This has no effect if -f is given. The end date defaults to
201 today, and the start date defaults to one day before the end date.
203 -r: The reason for voiding the payments, to be stored in the database.
205 -g: The FS::payment_gateway number for the gateway that handled
206 these payments. If -f is not given, this determines which
207 gateway will be contacted. This overrides -a.
209 -a: The agentnum whose default gateway will be used. If neither -a
210 nor -g is given, the system default gateway will be used.
212 -c: Use the default gateway for check transactions rather than
217 -X: Automatically cancel all packages belonging to customers whose
218 payments were returned. Requires a cancellation reasonnum
223 Given 'returns.txt', which contains one authorization number on each
224 line, provided by your default e-check processor:
226 freeside-void-payments -f returns.txt -c -r 'Returned check'
228 If your default processor is Western ACH, which supports automated
229 returns processing, this voids all returned payments since 2009-06-01:
231 freeside-void-payments -r 'Returned check' -s 2009-06-01
233 This, in your crontab, will void returned payments for the last
234 day at 8:30 every morning:
236 30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'
240 Most payment gateways don't support it.
244 L<Business::OnlinePayment>, L<FS::cust_pay>