bb2c2588dbe8948a9f44122c57fe1f19379443dd
[freeside.git] / FS / FS / pay_batch / paymentech.pm
1 package FS::pay_batch::paymentech;
2
3 use strict;
4 use vars qw(@ISA %import_info %export_info $name);
5 use FS::Record 'qsearchs';
6 use Time::Local;
7 use Date::Format 'time2str';
8 use Date::Parse 'str2time';
9 use Tie::IxHash;
10 use FS::Conf;
11 use Unicode::Truncate 'truncate_egc';
12
13 my $conf;
14 my ($bin, $merchantID, $terminalID, $username, $password, $with_recurringInd);
15 $name = 'paymentech';
16
17 my $gateway;
18
19 %import_info = (
20   filetype    => 'XML',
21   xmlrow         => [ qw(transResponse newOrderResp) ],
22   fields      => [
23     'paybatchnum',
24     '_date',
25     'approvalStatus',
26     'order_number',
27     'auth',
28     'procStatus',
29     'procStatusMessage',
30     'respCodeMessage',
31     ],
32   xmlkeys     => [
33     'orderID',
34     'respDateTime',
35     'approvalStatus',
36     'txRefNum',
37     'authorizationCode',
38     'procStatus',
39     'procStatusMessage',
40     'respCodeMessage',
41     ],
42   'hook'        => sub {
43       if ( !$gateway ) {
44         # find a gateway configuration that has the same merchantID 
45         # as the batch config, if there is one.  If not, leave 
46         # gateway out entirely.
47         my $merchant = (FS::Conf->new->config('batchconfig-paymentech'))[2];
48         $gateway = qsearchs({
49               'table'     => 'payment_gateway',
50               'addl_from' => ' JOIN payment_gateway_option USING (gatewaynum) ',
51               'hashref'   => {  disabled    => '',
52                                 optionname  => 'merchant_id',
53                                 optionvalue => $merchant,
54                               },
55               });
56       }
57       my ($hash, $oldhash) = @_;
58       $hash->{'gatewaynum'} = $gateway->gatewaynum if $gateway;
59       $hash->{'processor'} = 'PaymenTech';
60       my ($mon, $day, $year, $hour, $min, $sec) = 
61         $hash->{'_date'} =~ /^(..)(..)(....)(..)(..)(..)$/;
62       $hash->{'_date'} = timelocal($sec, $min, $hour, $day, $mon-1, $year);
63       $hash->{'paid'} = $oldhash->{'amount'};
64       if ( $hash->{'procStatus'} == 0 ) {
65         $hash->{'error_message'} = $hash->{'respCodeMessage'};
66       } else {
67         $hash->{'error_message'} = $hash->{'procStatusMessage'};
68       }
69     },
70   'approved'    => sub { my $hash = shift;
71                             $hash->{'approvalStatus'} 
72     },
73   'declined'    => sub { my $hash = shift;
74                             ! $hash->{'approvalStatus'} 
75     },
76 );
77
78 my %paytype = (
79   'personal checking' => 'C',
80   'personal savings'  => 'S',
81   'business checking' => 'X',
82   'business savings'  => 'X',
83 );
84
85 my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
86
87 %export_info = (
88   init  => sub {
89 # Load this at run time
90     eval "use XML::Writer";
91     die $@ if $@;
92     my $conf = shift;
93     ($bin, $terminalID, $merchantID, $username, $password, $with_recurringInd) =
94        $conf->config('batchconfig-paymentech');
95     },
96 # Here we do all the work in the header function.
97   header => sub {
98     my $pay_batch = shift;
99     my @cust_pay_batch = @{(shift)};
100     my $count = 1;
101     my $output;
102     my $xml = XML::Writer->new(
103       OUTPUT => \$output,
104       DATA_MODE => 1,
105       DATA_INDENT => 2,
106       ENCODING => 'utf-8'
107     );
108     $xml->xmlDecl(); # it is in the spec
109     $xml->startTag('transRequest', RequestCount => scalar(@cust_pay_batch) + 1);
110     $xml->startTag('batchFileID');
111     $xml->dataElement(userID => $username);
112     $xml->dataElement(fileDateTime => time2str('%Y%m%d%H%M%S', time));
113     $xml->dataElement(fileID => 'FILEID');
114     $xml->endTag('batchFileID');
115
116     foreach (@cust_pay_batch) {
117       $xml->startTag('newOrder', BatchRequestNo => $count++);
118       my $status = $_->cust_main->status;
119       tie my %order, 'Tie::IxHash', (
120         industryType    => 'EC',
121         transType       => 'AC',
122         bin             => $bin,
123         merchantID      => $merchantID,
124         terminalID      => $terminalID,
125         ($_->payby eq 'CARD') ? (
126           ccAccountNum    => $_->payinfo,
127           ccExp           => $_->expmmyy,
128         ) : (
129           ecpCheckRT      => ($_->payinfo =~ /@(\d+)/),
130           ecpCheckDDA     => ($_->payinfo =~ /(\d+)@/),
131           ecpBankAcctType => $paytype{lc($_->paytype)},
132           ecpDelvMethod   => 'A',
133         ),
134                            # truncate_egc will die() on empty string
135         avsZip      => $_->zip      ? truncate_egc($_->zip,      10) : undef,
136         avsAddress1 => $_->address1 ? truncate_egc($_->address1, 30) : undef,
137         avsAddress2 => $_->address2 ? truncate_egc($_->address2, 30) : undef,
138         avsCity     => $_->city     ? truncate_egc($_->city,     20) : undef,
139         avsState    => $_->state    ? truncate_egc($_->state,     2) : undef,
140         avsName     => ($_->first || $_->last)
141                        ? truncate_egc($_->first. ' '. $_->last, 30) : undef,
142         ( $paymentech_countries{ $_->country }
143           ? ( avsCountryCode  => $_->country )
144           : ()
145         ),
146         orderID           => $_->paybatchnum,
147         amount            => $_->amount * 100,
148         );
149       # only do this if recurringInd is enabled in config, 
150       # and the customer has at least one non-canceled recurring package
151       if ( $with_recurringInd and $status =~ /^active|suspended|ordered$/ ) {
152         # then send RF if this is the first payment on this payinfo,
153         # RS otherwise.
154         $order{'recurringInd'} = $_->payinfo_used ? 'RS' : 'RF';
155       }
156       foreach my $key (keys %order) {
157         $xml->dataElement($key, $order{$key})
158       }
159       $xml->endTag('newOrder');
160     }
161     $xml->startTag('endOfDay', BatchRequestNo => $count);
162     $xml->dataElement(bin => $bin);
163     $xml->dataElement(merchantID => $merchantID);
164     $xml->dataElement(terminalID => $terminalID);
165     $xml->endTag('endOfDay');
166     $xml->endTag('transRequest');
167     return $output;
168   },
169   row => sub {},
170 );
171
172 # Including this means that there is a Business::BatchPayment module for
173 # this gateway and we want to upgrade it.
174 # Must return the name of the module, followed by a hash of options.
175
176 sub _upgrade_gateway {
177   my $conf = FS::Conf->new;
178   my @batchconfig = $conf->config('batchconfig-paymentech');
179   my %options;
180   @options{ qw(
181     bin
182     terminalID
183     merchantID
184     login
185     password
186     with_recurringInd
187   ) } = @batchconfig;
188   $options{'industryType'} = 'EC';
189   ( 'Paymentech', %options );
190 }
191
192 1;