further optimize condition_sql for "Invoice eligible for automatic collection" condit...
[freeside.git] / FS / FS / Cron / pay_batch.pm
1 package FS::Cron::pay_batch;
2
3 use strict;
4 use vars qw( @ISA @EXPORT_OK $me $DEBUG );
5 use Exporter;
6 use Date::Format;
7 use FS::UID qw(dbh);
8 use FS::Record qw( qsearch qsearchs );
9 use FS::Conf;
10 use FS::queue;
11 use FS::agent;
12
13 @ISA = qw( Exporter );
14 @EXPORT_OK = qw ( pay_batch_submit pay_batch_receive );
15 $DEBUG = 0;
16 $me = '[FS::Cron::pay_batch]';
17
18 #freeside-daily %opt:
19 #  -v: enable debugging
20 #  -l: debugging level
21 #  -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
22 #  -r: Multi-process mode dry run option
23 #  -a: Only process customers with the specified agentnum
24
25 sub batch_gateways {
26   my $conf = FS::Conf->new;
27   # returns a list of arrayrefs: [ gateway, payby, agentnum ]
28   my %opt = @_;
29   my @agentnums;
30   if ( $conf->exists('batch-spoolagent') ) {
31     if ( $opt{a} ) {
32       @agentnums = split(',', $opt{a});
33     } else {
34       @agentnums = map { $_->agentnum } qsearch('agent');
35     }
36   } else {
37     @agentnums = ('');
38     if ( $opt{a} ) {
39       warn "Payment batch processing skipped in per-agent mode.\n" if $DEBUG;
40       return;
41     }
42   }
43   my @return;
44   foreach my $agentnum (@agentnums) {
45     my %gateways;
46     foreach my $payby ('CARD', 'CHEK') {
47       my $gatewaynum = $conf->config("batch-gateway-$payby", $agentnum);
48       next if !$gatewaynum;
49       my $gateway = FS::payment_gateway->by_key($gatewaynum)
50         or die "payment_gateway '$gatewaynum' not found\n";
51       push @return, [ $gateway, $payby, $agentnum ];
52     }
53   }
54   @return;
55 }
56
57 sub pay_batch_submit {
58   my %opt = @_;
59   local $DEBUG = ($opt{l} || 1) if $opt{v};
60   # if anything goes wrong, don't try to roll back previously submitted batches
61   local $FS::UID::AutoCommit = 1;
62   
63   my $dbh = dbh;
64
65   warn "$me batch_submit\n" if $DEBUG;
66   foreach my $config (batch_gateways(%opt)) {
67     my ($gateway, $payby, $agentnum) = @$config;
68     if ( $gateway->batch_processor->can('default_transport') ) {
69
70       my $search = { status => 'O', payby => $payby };
71       $search->{agentnum} = $agentnum if $agentnum;
72
73       foreach my $pay_batch ( qsearch('pay_batch', $search) ) {
74
75         warn "Exporting batch ".$pay_batch->batchnum."\n" if $DEBUG;
76         eval { $pay_batch->export_to_gateway( $gateway, debug => $DEBUG ); };
77
78         if ( $@ ) {
79           # warn the error and continue. rolling back the transaction once 
80           # we've started sending batches is bad.
81           warn "error submitting batch ".$pay_batch->batchnum." to gateway '".
82           $gateway->label."': $@\n";
83         }
84       }
85
86     } else { #can't(default_transport)
87       warn "Payment gateway '".$gateway->label.
88       "' doesn't support automatic transport; skipped.\n";
89     }
90   } #$payby
91
92   1;
93 }
94
95 sub pay_batch_receive {
96   my %opt = @_;
97   local $DEBUG = ($opt{l} || 1) if $opt{v};
98   local $FS::UID::AutoCommit = 0;
99
100   my $dbh = dbh;
101   my $error;
102
103   warn "$me batch_receive\n" if $DEBUG;
104
105   my %gateway_done;
106     # If a gateway is selected for more than one payby+agentnum, still
107     # only import from it once; we expect it will send back multiple
108     # result batches.
109   foreach my $config (batch_gateways(%opt)) {
110     my ($gateway, $payby, $agentnum) = @$config;
111     next if $gateway_done{$gateway->gatewaynum};
112     next unless $gateway->batch_processor->can('default_transport');
113     # already warned about this above
114     warn "Importing results from '".$gateway->label."'\n" if $DEBUG;
115     # Note that import_from_gateway is not agent-limited; if a gateway
116     # returns results for batches not associated with this agent, we will
117     # still accept them. Well-behaved gateways will not do that.
118     $error = eval { 
119       FS::pay_batch->import_from_gateway( gateway =>$gateway, debug => $DEBUG ) 
120     } || $@;
121     if ( $error ) {
122       # this we can roll back
123       $dbh->rollback;
124       die "error receiving from gateway '".$gateway->label."':\n$error\n";
125     }
126   } #$gateway
127
128   # resolve batches if we can
129   foreach my $pay_batch (qsearch('pay_batch', { status => 'I' })) {
130     warn "Trying to resolve batch ".$pay_batch->batchnum."\n" if $DEBUG;
131     $error = $pay_batch->try_to_resolve;
132     if ( $error ) {
133       $dbh->rollback;
134       die "unable to resolve batch ".$pay_batch->batchnum.":\n$error\n";
135     }
136   }
137
138   $dbh->commit;
139 }
140 1;