df0a71785b509cee684a07853d7d45ba862edf32
[Business-OnlinePayment-IATSPayments.git] / lib / Business / OnlinePayment / IATSPayments.pm
1 package Business::OnlinePayment::IATSPayments;
2 use base qw( Business::OnlinePayment );
3
4 use warnings;
5 use strict;
6 use Data::Dumper;
7 use Business::CreditCard;
8 use SOAP::Lite;
9 #SOAP::Lite->import(+trace=>'debug');
10
11 our $VERSION = '0.03_01';
12 $VERSION = eval $VERSION; # modperlstyle: convert the string into a number
13
14 sub _info {
15   {
16     'info_compat'       => '0.01',
17     'gateway_name'      => 'IATS Payments',
18     'gateway_url'       => 'http://home.iatspayments.com/',
19     'module_version'    => $VERSION,
20     'supported_types'   => [ 'CC', 'ECHECK' ],
21     #'token_support'     => 1,
22     'test_transaction'  => 1,
23
24     'supported_actions' => [ 'Normal Authorization',
25                              'Credit',
26                            ],
27   };
28 }
29
30 sub set_defaults {
31     my $self = shift;
32     #my %opts = @_;
33
34     #$self->build_subs(qw( order_number avs_code cvv2_response
35     #                      response_page response_code response_headers
36     #                 ));
37
38     $self->build_subs(qw( avs_code ));
39
40 }
41
42 sub map_fields {
43     my($self) = @_;
44
45     my %content = $self->content();
46
47     # TYPE MAP
48     my %types = ( 'visa'               => 'CC',
49                   'mastercard'         => 'CC',
50                   'american express'   => 'CC',
51                   'discover'           => 'CC',
52                   'check'              => 'ECHECK',
53                 );
54     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
55     $self->transaction_type($content{'type'});
56     
57     # ACTION MAP 
58     my $action = lc($content{'action'});
59     my %actions =
60       ( 'normal authorization'  => 'ProcessCreditCardV1',
61         'credit'                => 'ProcessCreditCardRefundWithTransactionIdV1',
62       );
63     my %check_actions =
64       ( 'normal authorization'  => 'ProcessACHEFTV1',
65         'credit'                => 'ProcessACHEFTRefundWithTransactionIdV1',
66       );
67
68     if ($self->transaction_type eq 'CC') {
69       $content{'action'} = $actions{$action} || $action;
70     } elsif ($self->transaction_type eq 'ECHECK') {
71
72       $content{'action'} = $check_actions{$action} || $action;
73
74       # ACCOUNT TYPE MAP
75       my %account_types = ('personal checking'   => 'CHECKING',
76                            'personal savings'    => 'SAVINGS',
77                            'business checking'   => 'CHECKING',
78                            'business savings'    => 'SAVINGS',
79                            #not technically B:OP valid i guess?
80                            'checking'            => 'CHECKING',
81                            'savings'             => 'SAVINGS',
82                           );
83       $content{'account_type'} = $account_types{lc($content{'account_type'})}
84                                  || $content{'account_type'};
85     }
86
87     # stuff it back into %content
88     $self->content(%content);
89
90 }
91
92 sub remap_fields {
93     my($self,%map) = @_;
94
95     my %content = $self->content();
96     foreach(keys %map) {
97         $content{$map{$_}} = $content{$_};
98     }
99     $self->content(%content);
100 }
101
102 # NA: VISA, MC, AMX, DSC
103 # UK: VISA, MC, AMX, MAESTR
104 our %mop = (
105   'VISA card'             => 'VISA',
106   'MasterCard'            => 'MC',
107   'Discover card'         => 'DSC',
108   'American Express card' => 'AMEX',
109   'Switch'                => 'MAESTR',
110   'Solo'                  => 'MAESTR',
111 );
112
113 #https://www.iatspayments.com/english/help/rejects.html
114 our %reject = (
115   '1' => 'Agent code has not been set up on the authorization system. Please call iATS at 1-888-955-5455.',
116   '2' => 'Unable to process transaction. Verify and re-enter credit card information.',
117   '3' => 'Invalid Customer Code.',
118   '4' => 'Incorrect expiration date.',
119   '5' => 'Invalid transaction. Verify and re-enter credit card information.',
120   '6' => 'Please have cardholder call the number on the back of the card.',
121   '7' => 'Lost or stolen card.',
122   '8' => 'Invalid card status.',
123   '9' => 'Restricted card status. Usually on corporate cards restricted to specific sales.',
124   '10' => 'Error. Please verify and re-enter credit card information.',
125   '11' => 'General decline code. Please have client call the number on the back of credit card',
126   '12' => 'Incorrect CVV2 or Expiry date',
127   '14' => 'The card is over the limit.',
128   '15' => 'General decline code. Please have client call the number on the back of credit card',
129   '16' => 'Invalid charge card number. Verify and re-enter credit card information.',
130   '17' => 'Unable to authorize transaction. Authorizer needs more information for approval.',
131   '18' => 'Card not supported by institution.',
132   '19' => 'Incorrect CVV2 security code',
133   '22' => 'Bank timeout. Bank lines may be down or busy. Re-try transaction later.',
134   '23' => 'System error. Re-try transaction later.',
135   '24' => 'Charge card expired.',
136   '25' => 'Capture card. Reported lost or stolen.',
137   '26' => 'Invalid transaction, invalid expiry date. Please confirm and retry transaction.',
138   '27' => 'Please have cardholder call the number on the back of the card.',
139   '32' => 'Invalid charge card number.',
140   '39' => 'Contact IATS 1-888-955-5455.',
141   '40' => 'Invalid card number. Card not supported by IATS.',
142   '41' => 'Invalid Expiry date.',
143   '42' => 'CVV2 required.',
144   '43' => 'Incorrect AVS.',
145   '45' => 'Credit card name blocked. Call iATS at 1-888-955-5455.',
146   '46' => 'Card tumbling. Call iATS at 1-888-955-5455.',
147   '47' => 'Name tumbling. Call iATS at 1-888-955-5455.',
148   '48' => 'IP blocked. Call iATS at 1-888-955-5455.',
149   '49' => 'Velocity 1 – IP block. Call iATS at 1-888-955-5455.',
150   '50' => 'Velocity 2 – IP block. Call iATS at 1-888-955-5455.',
151   '51' => 'Velocity 3 – IP block. Call iATS at 1-888-955-5455.',
152   '52' => 'Credit card BIN country blocked. Call iATS at 1-888-955-5455.',
153   '100' => 'DO NOT REPROCESS. Call iATS at 1-888-955-5455.',
154   #Timeout      The system has not responded in the time allotted. Call iATS at 1-888-955-5455.
155 );
156
157 our %failure_status = (
158   '7'  => 'stolen',
159   '8'  => 'inactive',
160   '9'  => 'inactive',
161   '14' => 'nsf',
162   '24' => 'expired',
163   '25' => 'stolen',
164   '45' => 'blacklisted',
165   '48' => 'blacklisted',
166   '49' => 'blacklisted',
167   '50' => 'blacklisted',
168   '51' => 'blacklisted',
169   '52' => 'blacklisted',
170   #'100' => # it sounds serious.  but why?  it says nothing specific
171 );
172
173 sub submit {
174   my($self) = @_;
175
176   $self->map_fields;
177
178   $self->remap_fields(
179         login             => 'agentCode',
180         password          => 'password',
181
182         description       => 'comment',
183         amount            => 'total',
184         invoice_number    => 'invoiceNum',
185         customer_ip       => 'customerIPAddress',
186
187         last_name         => 'lastName',
188         first_name        => 'firstName',
189         address           => 'address',
190         city              => 'city',
191         state             => 'state',
192         zip               => 'zipCode',
193         #country           => 'x_Country',
194
195         card_number       => 'creditCardNum',
196         expiration        => 'creditCardExpiry',
197         cvv2              => 'cvv2',
198
199         authorization     => 'transactionId',
200
201         account_type      => 'accountType',
202
203   );
204
205   my %content = $self->content();
206
207   $content{'mop'} = $mop{ cardtype($content{creditCardNum}) }
208     if $content{'type'} eq 'CC';
209
210   if ( $self->test_transaction ) {
211     $content{agentCode} = 'TEST88';
212     $content{password}  = 'TEST88';
213   }
214
215   my $base_uri =
216     ( ! $content{currency} || $content{currency} =~ /^(USD|CAD)$/i )
217       ? 'https://www.iatspayments.com/NetGate/'
218       : 'https://www.uk.iatspayments.com/NetGate/';
219
220   my $action = $content{action};
221
222   my $uri = $base_uri. "ProcessLink.asmx?op=$action";
223
224   # never "uk.iatspayments.com"
225   my $default_ns = 'https://www.iatspayments.com/NetGate/';
226
227   my %data = map { $_ => $content{$_} } (qw(
228     agentCode
229     password
230     comment
231     total
232     customerIPAddress
233   ));
234
235   if ( $action =~ /RefundWithTransacdtionIdV[\d\.]+$/ ) {
236
237     $data{ $_ } = $content{$_} for qw(
238       transactionId
239     );
240
241   } else {
242
243     $data{ $_ } = $content{$_} for qw(
244       invoiceNum
245       lastName
246       firstName
247       address
248       city
249       state
250       zipCode
251     );
252
253     if ( $content{'type'} eq 'CC' ) {
254
255       $data{$_} = $content{$_}
256         for qw( creditCardNum creditCardExpiry cvv2 mop );
257
258     } elsif ( $content{'type'} eq 'ECHECK' ) {
259
260       $data{'accountNum'}= $content{'routing_code'}. $content{'account_number'};
261
262       $data{$_} = $content{$_}
263         for qw( accountType );
264
265     }
266
267   }
268
269   my @opts = map { SOAP::Data->name($_)->value( $data{$_} ) }
270                keys %data;
271
272   my $result = SOAP::Lite
273                  ->proxy($uri)
274                  ->default_ns($default_ns)
275                  #->on_action( sub { join '/', @_ } )
276                  ->on_action( sub { join '', @_ } )
277                  ->autotype(0)
278
279                  ->$action( @opts )
280
281                  ->result();
282
283   my $iatsresponse = $result->{IATSRESPONSE};
284
285   if ( $iatsresponse->{STATUS} eq 'Failure' && $iatsresponse->{ERRORS} ) {
286     die 'iATS Payments error: '. $iatsresponse->{ERRORS}. "\n";
287   } elsif ( $iatsresponse->{STATUS} ne 'Success' ) {
288     die "Couldn't parse iATS Payments response: ". Dumper($result);
289   }
290
291   my $processresult = $iatsresponse->{PROCESSRESULT};
292
293   if ( defined( $processresult->{TRANSACTIONID} ) ) {
294     $processresult->{TRANSACTIONID} =~ s/^\s+//;
295     $processresult->{TRANSACTIONID} =~ s/\s+$//;
296   }
297   $self->authorization($processresult->{TRANSACTIONID} || '');
298
299   if ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*OK(:\s*\w+:)?(\w)?\s*$/i ) {
300     $self->is_success(1);
301     $self->avs_code($2);
302
303   } elsif ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*Timeout\s*$/i ) {
304     $self->is_success(0);
305     $self->error_message('The system has not responded in the time allotted. '.
306                          'Call iATS at 1-888-955-5455.');
307
308   } elsif ( $processresult->{AUTHORIZATIONRESULT}
309               =~ /^\s*REJ(ECT)?:\s*(\d+)\s*$/i
310           )
311   {
312     $self->is_success(0);
313     $self->result_code($2);
314     $self->error_message( $reject{$2} || $processresult->{AUTHORIZATIONRESULT});
315     $self->failure_status( $failure_status{$2} || 'decline' );
316
317   } else {
318     die "No/Unknown AUTHORIZATIONRESULT iATS Payments response: ".
319           Dumper($processresult);
320   }
321
322 }
323
324 1;
325
326 __END__
327
328 =head1 NAME
329
330 Business::OnlinePayment::IATSPayments - IATS Payments backend for Business::OnlinePayment
331
332 =head1 SYNOPSIS
333
334   use Business::OnlinePayment;
335
336   my $tx =
337     new Business::OnlinePayment( 'IATSPayments' );
338
339   $tx->content(
340       login          => 'TEST88', # agentCode
341       password       => 'TEST88', #password 
342
343       type           => 'CC',
344       action         => 'Normal Authorization',
345       amount         => '1.00',
346
347       first_name     => 'Tofu',
348       last_name      => 'Beast',
349       address        => '123 Anystreet',
350       city           => 'Anywhere',
351       state          => 'UT',
352       zip            => '84058',
353
354       card_number    => '4111111111111111',
355       expiration     => '09/20',
356       cvv2           => '124',
357
358       #optional
359       description    => 'Business::OnlinePayment test',
360       customer_ip    => '1.2.3.4',
361       invoice_num    => 54,
362   );
363   $tx->submit();
364
365   if($tx->is_success()) {
366       print "Card processed successfully: ".$tx->authorization."\n";
367   } else {
368       print "Card was rejected: ".$tx->error_message."\n";
369   }
370
371 =head1 SUPPORTED TRANSACTION TYPES
372
373 =head2 CC, Visa, MasterCard, American Express, Discover
374
375 Content required: type, login, action, amount, card_number, expiration.
376
377 =head2 Check
378
379 Content required: type, login, action, amount, name, account_number, routing_code.
380
381 =head1 DESCRIPTION
382
383 For detailed information see L<Business::OnlinePayment>.
384
385 =head1 METHODS AND FUNCTIONS
386
387 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
388
389 =head2 result_code
390
391 Returns the response error code.
392
393 =head2 error_message
394
395 Returns the response error number.
396
397 =head2 action
398
399 The following actions are valid
400
401   Normal Authorization
402   Credit
403
404 =head1 COMPATIBILITY
405
406 Business::OnlinePayment::IATSPayments uses iATS WebServices ProcessLink 4.0
407 and (for tokenization support) iATS WebServices CustomerLink 4.0.
408
409 =head1 AUTHORS
410
411 Ivan Kohler <ivan-iatspayments@freeside.biz>
412
413 =head1 SEE ALSO
414
415 perl(1). L<Business::OnlinePayment>.
416
417 =cut
418