69495ab2449afb19d5c274aa0dd46317316260f9
[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.02';
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   my %data = map { $_ => $content{$_} } (qw(
225     agentCode
226     password
227     comment
228     total
229     customerIPAddress
230   ));
231
232   if ( $action =~ /RefundWithTransacdtionIdV[\d\.]+$/ ) {
233
234     $data{ $_ } = $content{$_} for qw(
235       transactionId
236     );
237
238   } else {
239
240     $data{ $_ } = $content{$_} for qw(
241       invoiceNum
242       lastName
243       firstName
244       address
245       city
246       state
247       zipCode
248     );
249
250     if ( $content{'type'} eq 'CC' ) {
251
252       $data{$_} = $content{$_}
253         for qw( creditCardNum creditCardExpiry cvv2 mop );
254
255     } elsif ( $content{'type'} eq 'ECHECK' ) {
256
257       $data{'accountNum'}= $content{'routing_code'}. $content{'account_number'};
258
259       $data{$_} = $content{$_}
260         for qw( accountType );
261
262     }
263
264   }
265
266   my @opts = map { SOAP::Data->name($_)->value( $data{$_} ) }
267                keys %data;
268
269   my $result = SOAP::Lite
270                  ->proxy($uri)
271                  ->default_ns($base_uri)
272                  #->on_action( sub { join '/', @_ } )
273                  ->on_action( sub { join '', @_ } )
274                  ->autotype(0)
275
276                  ->$action( @opts )
277
278                  ->result();
279
280   my $iatsresponse = $result->{IATSRESPONSE};
281
282   if ( $iatsresponse->{STATUS} eq 'Failure' && $iatsresponse->{ERRORS} ) {
283     die 'iATS Payments error: '. $iatsresponse->{ERRORS}. "\n";
284   } elsif ( $iatsresponse->{STATUS} ne 'Success' ) {
285     die "Couldn't parse iATS Payments response: ". Dumper($result);
286   }
287
288   my $processresult = $iatsresponse->{PROCESSRESULT};
289
290   $processresult->{TRANSACTIONID} =~ s/^\s+//;
291   $processresult->{TRANSACTIONID} =~ s/\s+$//;
292   $self->authorization($processresult->{TRANSACTIONID} || '');
293
294   if ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*OK(:\s*\d+:)?(\w)?\s*$/i ) {
295     $self->is_success(1);
296     $self->avs_code($2); #avs_code?  sure looks like one
297
298   } elsif ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*Timeout\s*$/i ) {
299     $self->is_success(0);
300     $self->error_message('The system has not responded in the time allotted. '.
301                          'Call iATS at 1-888-955-5455.');
302
303   } elsif ( $processresult->{AUTHORIZATIONRESULT}
304               =~ /^\s*REJ(ECT)?:\s*(\d+)\s*$/i
305           )
306   {
307     $self->is_success(0);
308     $self->result_code($2);
309     $self->error_message( $reject{$2} || $processresult->{AUTHORIZATIONRESULT});
310     $self->failure_status( $failure_status{$2} || 'decline' );
311
312   } else {
313     die "No/Unknown AUTHORIZATIONRESULT iATS Payments response: ".
314           Dumper($processresult);
315   }
316
317 }
318
319 1;
320
321 __END__
322
323 =head1 NAME
324
325 Business::OnlinePayment::IATSPayments - IATS Payments backend for Business::OnlinePayment
326
327 =head1 SYNOPSIS
328
329   use Business::OnlinePayment;
330
331   my $tx =
332     new Business::OnlinePayment( 'IATSPayments' );
333
334   $tx->content(
335       login          => 'TEST88', # agentCode
336       password       => 'TEST88', #password 
337
338       type           => 'CC',
339       action         => 'Normal Authorization',
340       amount         => '1.00',
341
342       first_name     => 'Tofu',
343       last_name      => 'Beast',
344       address        => '123 Anystreet',
345       city           => 'Anywhere',
346       state          => 'UT',
347       zip            => '84058',
348
349       card_number    => '4111111111111111',
350       expiration     => '09/20',
351       cvv2           => '124',
352
353       #optional
354       description    => 'Business::OnlinePayment test',
355       customer_ip    => '1.2.3.4',
356       invoice_num    => 54,
357   );
358   $tx->submit();
359
360   if($tx->is_success()) {
361       print "Card processed successfully: ".$tx->authorization."\n";
362   } else {
363       print "Card was rejected: ".$tx->error_message."\n";
364   }
365
366 =head1 SUPPORTED TRANSACTION TYPES
367
368 =head2 CC, Visa, MasterCard, American Express, Discover
369
370 Content required: type, login, action, amount, card_number, expiration.
371
372 =head2 Check
373
374 Content required: type, login, action, amount, name, account_number, routing_code.
375
376 =head1 DESCRIPTION
377
378 For detailed information see L<Business::OnlinePayment>.
379
380 =head1 METHODS AND FUNCTIONS
381
382 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
383
384 =head2 result_code
385
386 Returns the response error code.
387
388 =head2 error_message
389
390 Returns the response error number.
391
392 =head2 action
393
394 The following actions are valid
395
396   Normal Authorization
397   Credit
398
399 =head1 COMPATIBILITY
400
401 Business::OnlinePayment::IATSPayments uses iATS WebServices ProcessLink 4.0
402 and (for tokenization support) iATS WebServices CustomerLink 4.0.
403
404 =head1 AUTHORS
405
406 Ivan Kohler <ivan-iatspayments@freeside.biz>
407
408 =head1 SEE ALSO
409
410 perl(1). L<Business::OnlinePayment>.
411
412 =cut
413