summaryrefslogtreecommitdiff
path: root/FS/bin/freeside-void-payments
blob: 18fed05a7eb319627490528df8e666b55dfc7c89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/perl -w

use strict;
use vars qw( $user $cust_main @customers );
use Getopt::Std;
use FS::UID qw(adminsuidsetup);
use FS::Record qw(qsearchs);
use FS::Conf;
use FS::cust_main;
use FS::cust_pay;
use Business::OnlinePayment; # For retrieving the void list only.
use Time::Local;
use Date::Parse 'str2time';
use Date::Format 'time2str';

my %opt;
getopts("f:ca:g:s:e:", \%opt);

$user = shift or die &usage;
&adminsuidsetup( $user );

# The -g and -a options need to override this.
my $method = $opt{'c'} ? 'ECHECK' : 'CARD';
my $gateway;
if($opt{'g'}) {
  $gateway = FS::payment_gateway->by_key($opt{'g'})
    or die "Payment gateway not found: '".$opt{'g'}."'.";
}
elsif($opt{'a'}) {
  my $agent = FS::agent->by_key($opt{'a'})
    or die "Agent not found: '".$opt{'a'}."'.";
  $gateway = $agent->payment_gateway(method => $method)
    or die "Agent has no payment gateway for method '$method'.";
}

my ($processor, $login, $password, $action, @bop_options) =
  FS::cust_main->default_payment_gateway($method);
my $gatewaynum = '';

if($gateway) {
# override the default gateway
  $gatewaynum = $gateway->gatewaynum . '-' if $gateway->gatewaynum;
  $processor = $gateway->gateway_module;
  $login     = $gateway->gateway_username;
  $password  = $gateway->gateway_password;
  $action    = $gateway->gateway_action;
  @bop_options = $gateway->options;
}

my @auths;
if($opt{'f'}) {
# Read the list of authorization numbers from a file.
  my $in;
  open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
  @auths = grep /^\d+$/, <$in>;
  chomp @auths;
}
else {
# Get the list from the processor.  This requires the processor module to 
# support get_returns.
  my $transaction = new Business::OnlinePayment ( $processor, @bop_options );
  if(! $transaction->can('get_returns')) {
    die "'$processor' does not provide an automated void list.";
  }
  my @local = localtime;
# Start and end dates for this can be set via -s and -e.  If they're not,
# end defaults to midnight today and start defaults to one day before end.
  my $end = defined($opt{'e'}) ? 
      str2time($opt{'e'}) : timelocal(0, 0, 0, @local[3,4,5]);
  my $start = defined($opt{'s'}) ?
      str2time($opt{'s'}) : $end - 86400;
  die "Invalid date range: '$start'-'$end'" if not ($start and $end);
  $transaction->content (
    login     => $login,
    password  => $password,
    start     => time2str("%Y-%m-%d",$start),
    end       => time2str("%Y-%m-%d",$end),
    );
  @auths = $transaction->get_returns;
}

$opt{'r'} ||= 'freeside-void-payments';

foreach my $authnum (@auths) {
  my $paybatch = $gatewaynum . $processor . ':' . $authnum;
  my $cust_pay = qsearchs('cust_pay', { paybatch => $paybatch } );
  if($cust_pay) {
    $cust_pay->void($opt{'r'});
  }
  else {
    warn "cust_pay record not found: '$paybatch'";
  }
}

sub usage {
    die "Usage:\n\n  freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] user\n";
}

__END__

# Documentation

=head1 NAME

freeside-void-payments - Automatically void a list of returned payments.

=head1 SYNOPSIS

  freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ] [ -r 'reason' ] [ -g gatewaynum | -a agentnum ] [ -c ] user

=head1 DESCRIPTION

Voids payments that were returned by the payment processor.  Can be 
run periodically from crontab or manually after receiving a list of 
returned payments.  Normally this is a meaningful operation only for 
electronic checks.

This script voids payments based on the combination of gateway (see 
L<FS::payment_gateway>) and authorization number, since this is 
generally how the processor will identify them later.

  -f: Read the list of authorization numbers from the specified file.  
      If they are not from the default payment gateway, -g or -a 
      must be given to identify the gateway.
      
  If -f is not given, the script will attempt to contact the gateway 
  and download a list of returned transactions.  To support this, 
  the Business::OnlinePayment module for the processor must implement 
  the I<get_returns()> method.  For an example, see 
  L<Business::OnlinePayment::WesternACH>.

  -s, -e: Specify the starting and ending dates for the void list.  
      This has no effect if -f is given.  The end date defaults to 
      today, and the start date defaults to one day before the end date.

  -r: The reason for voiding the payments, to be stored in the database.

  -g: The L<FS::payment_gateway> number for the gateway that handled 
      these payments.  If -f is not given, this determines which 
      gateway will be contacted.  This overrides -a.

  -a: The agentnum whose default gateway will be used.  If neither -a 
      nor -g is given, the system default gateway will be used.

  -c: Use the default gateway for check transactions rather than 
      credit cards.

A warning will be emitted for each transaction that can't be found.  
This may happen if it's already been voided, or if the gateway 
doesn't match.

=head1 EXAMPLE

Given 'returns.txt', which contains one authorization number on each 
line, provided by your default e-check processor:

  freeside-void-payments -f returns.txt -c -r 'Returned check'

If your default processor is Western ACH, which supports automated 
returns processing, this voids all returned payments since 2009-06-01:

  freeside-void-payments -r 'Returned check' -s 2009-06-01

This, in your crontab, will void returned payments for the last 
day at 8:30 every morning:

  30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'

=head1 BUGS

Most payment gateways don't support it, making the script largely useless.

=head1 SEE ALSO

L<Business::OnlinePayment>, L<FS::cust_pay>

=cut