dd6dc5b0855a79a6aa97da0ab9b12ce091355afe
[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.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   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   $self->authorization($processresult->{TRANSACTIONID} || '');
291
292   if ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*OK(:\s*\d+:)?(\w)?\s*$/i ) {
293     $self->is_success(1);
294     $self->avs_code($2); #avs_code?  sure looks like one
295
296   } elsif ( $processresult->{AUTHORIZATIONRESULT} =~ /^\s*Timeout\s*$/i ) {
297     $self->is_success(0);
298     $self->error_message('The system has not responded in the time allotted. '.
299                          'Call iATS at 1-888-955-5455.');
300
301   } elsif ( $processresult->{AUTHORIZATIONRESULT}
302               =~ /^\s*REJ(ECT)?:\s*(\d+)\s*$/i
303           )
304   {
305     $self->is_success(0);
306     $self->error_message( $reject{$2} || $processresult->{AUTHORIZATIONRESULT});
307     $self->failure_status( $failure_status{$2} || 'decline' );
308
309   } else {
310     die "No/Unknown AUTHORIZATIONRESULT iATS Payments response: ".
311           Dumper($processresult);
312   }
313
314 }
315
316 1;
317
318 __END__
319
320 =head1 NAME
321
322 Business::OnlinePayment::IATSPayments - IATS Payments backend for Business::OnlinePayment
323
324 =head1 SYNOPSIS
325
326   use Business::OnlinePayment;
327
328   my $tx =
329     new Business::OnlinePayment( 'IATSPayments' );
330
331   $tx->content(
332       login          => 'TEST88', # agentCode
333       password       => 'TEST88', #password 
334
335       type           => 'CC',
336       action         => 'Normal Authorization',
337       amount         => '1.00',
338
339       first_name     => 'Tofu',
340       last_name      => 'Beast',
341       address        => '123 Anystreet',
342       city           => 'Anywhere',
343       state          => 'UT',
344       zip            => '84058',
345
346       card_number    => '4111111111111111',
347       expiration     => '09/20',
348       cvv2           => '124',
349
350       #optional
351       description    => 'Business::OnlinePayment test',
352       customer_ip    => '1.2.3.4',
353       invoice_num    => 54,
354   );
355   $tx->submit();
356
357   if($tx->is_success()) {
358       print "Card processed successfully: ".$tx->authorization."\n";
359   } else {
360       print "Card was rejected: ".$tx->error_message."\n";
361   }
362
363 =head1 SUPPORTED TRANSACTION TYPES
364
365 =head2 CC, Visa, MasterCard, American Express, Discover
366
367 Content required: type, login, action, amount, card_number, expiration.
368
369 =head2 Check
370
371 Content required: type, login, action, amount, name, account_number, routing_code.
372
373 =head1 DESCRIPTION
374
375 For detailed information see L<Business::OnlinePayment>.
376
377 =head1 METHODS AND FUNCTIONS
378
379 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
380
381 =head2 result_code
382
383 Returns the response error code.
384
385 =head2 error_message
386
387 Returns the response error number.
388
389 =head2 action
390
391 The following actions are valid
392
393   Normal Authorization
394   Credit
395
396 =head1 COMPATIBILITY
397
398 Business::OnlinePayment::IATSPayments uses iATS WebServices ProcessLink 4.0
399 and (for tokenization support) iATS WebServices CustomerLink 4.0.
400
401 =head1 AUTHORS
402
403 Ivan Kohler <ivan-iatspayments@freeside.biz>
404
405 =head1 SEE ALSO
406
407 perl(1). L<Business::OnlinePayment>.
408
409 =cut
410