Patch From Steve Simitzis for better compatiblity with eProcessingNetwork's Authorize...
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet.pm
1 package Business::OnlinePayment::AuthorizeNet;
2
3 use strict;
4 use Carp;
5 use Business::OnlinePayment;
6 use Net::SSLeay qw/make_form post_https make_headers/;
7 use Text::CSV_XS;
8 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
9
10 require Exporter;
11
12 @ISA = qw(Exporter Business::OnlinePayment);
13 @EXPORT = qw();
14 @EXPORT_OK = qw();
15 $VERSION = '3.18';
16
17 sub set_defaults {
18     my $self = shift;
19
20     $self->server('secure.authorize.net');
21     $self->port('443');
22     $self->path('/gateway/transact.dll');
23
24     $self->build_subs(qw( order_number md5 avs_code cvv2_response
25                           cavv_response
26                      ));
27 }
28
29 sub map_fields {
30     my($self) = @_;
31
32     my %content = $self->content();
33
34     # ACTION MAP
35     my %actions = ('normal authorization' => 'AUTH_CAPTURE',
36                    'authorization only'   => 'AUTH_ONLY',
37                    'credit'               => 'CREDIT',
38                    'post authorization'   => 'PRIOR_AUTH_CAPTURE',
39                    'void'                 => 'VOID',
40                   );
41     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
42
43     # TYPE MAP
44     my %types = ('visa'               => 'CC',
45                  'mastercard'         => 'CC',
46                  'american express'   => 'CC',
47                  'discover'           => 'CC',
48                  'check'              => 'ECHECK',
49                 );
50     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
51     $self->transaction_type($content{'type'});
52
53     # ACCOUNT TYPE MAP
54     my %account_types = ('personal checking'   => 'CHECKING',
55                          'personal savings'    => 'SAVINGS',
56                          'business checking'   => 'CHECKING',
57                          'business savings'    => 'SAVINGS',
58                         );
59     $content{'account_type'} = $account_types{lc($content{'account_type'})}
60                                || $content{'account_type'};
61
62     $content{'referer'} = defined( $content{'referer'} )
63                             ? make_headers( 'Referer' => $content{'referer'} )
64                             : "";
65
66     if (length $content{'password'} == 15) {
67         $content{'transaction_key'} = delete $content{'password'};
68     }
69
70     # stuff it back into %content
71     $self->content(%content);
72 }
73
74 sub remap_fields {
75     my($self,%map) = @_;
76
77     my %content = $self->content();
78     foreach(keys %map) {
79         $content{$map{$_}} = $content{$_};
80     }
81     $self->content(%content);
82 }
83
84 sub get_fields {
85     my($self,@fields) = @_;
86
87     my %content = $self->content();
88     my %new = ();
89     foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
90     return %new;
91 }
92
93 sub submit {
94     my($self) = @_;
95
96     $self->map_fields();
97     $self->remap_fields(
98         type              => 'x_Method',
99         login             => 'x_Login',
100         password          => 'x_Password',
101         transaction_key   => 'x_Tran_Key',
102         action            => 'x_Type',
103         description       => 'x_Description',
104         amount            => 'x_Amount',
105         currency          => 'x_Currency_Code',
106         invoice_number    => 'x_Invoice_Num',
107         order_number      => 'x_Trans_ID',
108         auth_code         => 'x_Auth_Code',
109         customer_id       => 'x_Cust_ID',
110         customer_ip       => 'x_Customer_IP',
111         last_name         => 'x_Last_Name',
112         first_name        => 'x_First_Name',
113         company           => 'x_Company',
114         address           => 'x_Address',
115         city              => 'x_City',
116         state             => 'x_State',
117         zip               => 'x_Zip',
118         country           => 'x_Country',
119         ship_last_name    => 'x_Ship_To_Last_Name',
120         ship_first_name   => 'x_Ship_To_First_Name',
121         ship_company      => 'x_Ship_To_Company',
122         ship_address      => 'x_Ship_To_Address',
123         ship_city         => 'x_Ship_To_City',
124         ship_state        => 'x_Ship_To_State',
125         ship_zip          => 'x_Ship_To_Zip',
126         ship_country      => 'x_Ship_To_Country',
127         phone             => 'x_Phone',
128         fax               => 'x_Fax',
129         email             => 'x_Email',
130         email_customer    => 'x_Email_Customer',
131         card_number       => 'x_Card_Num',
132         expiration        => 'x_Exp_Date',
133         cvv2              => 'x_Card_Code',
134         check_type        => 'x_Echeck_Type',
135         account_name      => 'x_Bank_Acct_Name',
136         account_number    => 'x_Bank_Acct_Num',
137         account_type      => 'x_Bank_Acct_Type',
138         bank_name         => 'x_Bank_Name',
139         routing_code      => 'x_Bank_ABA_Code',
140         customer_org      => 'x_Customer_Organization_Type', 
141         customer_ssn      => 'x_Customer_Tax_ID',
142         license_num       => 'x_Drivers_License_Num',
143         license_state     => 'x_Drivers_License_State',
144         license_dob       => 'x_Drivers_License_DOB',
145         recurring_billing => 'x_Recurring_Billing',
146     );
147
148     my $auth_type = $self->{_content}->{transaction_key}
149                       ? 'transaction_key'
150                       : 'password';
151
152     my @required_fields = ( qw(type action login), $auth_type );
153
154     unless ( $self->{_content}->{action} eq 'VOID' ) {
155
156       if ($self->transaction_type() eq "ECHECK") {
157
158         push @required_fields, qw(
159           amount routing_code account_number account_type bank_name
160           account_name
161         );
162
163         if (defined $self->{_content}->{customer_org} and
164             length  $self->{_content}->{customer_org}
165         ) {
166           push @required_fields, qw( customer_org customer_ssn );
167         } else {
168           push @required_fields, qw(license_num license_state license_dob);
169         }
170
171       } elsif ($self->transaction_type() eq 'CC' ) {
172
173         if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
174           if ( $self->{_content}->{order_number} ) {
175             push @required_fields, qw( amount order_number );
176           } else {
177             push @required_fields, qw( amount card_number expiration );
178           }
179         } elsif ( $self->{_content}->{action} eq 'CREDIT' ) {
180           push @required_fields, qw( amount order_number card_number );
181         } else {
182           push @required_fields, qw(
183             amount last_name first_name card_number expiration
184           );
185         }
186       } else {
187         Carp::croak( "AuthorizeNet can't handle transaction type: ".
188                      $self->transaction_type() );
189       }
190
191     }
192
193     $self->required_fields(@required_fields);
194
195     my %post_data = $self->get_fields(qw/
196         x_Login x_Password x_Tran_Key x_Invoice_Num
197         x_Description x_Amount x_Cust_ID x_Method x_Type x_Card_Num x_Exp_Date
198         x_Card_Code x_Auth_Code x_Echeck_Type x_Bank_Acct_Num
199         x_Bank_Account_Name x_Bank_ABA_Code x_Bank_Name x_Bank_Acct_Type
200         x_Customer_Organization_Type x_Customer_Tax_ID x_Customer_IP
201         x_Drivers_License_Num x_Drivers_License_State x_Drivers_License_DOB
202         x_Last_Name x_First_Name x_Company
203         x_Address x_City x_State x_Zip
204         x_Country
205         x_Ship_To_Last_Name x_Ship_To_First_Name x_Ship_To_Company
206         x_Ship_To_Address x_Ship_To_City x_Ship_To_State x_Ship_To_Zip
207         x_Ship_To_Country
208         x_Phone x_Fax x_Email x_Email_Customer x_Country
209         x_Currency_Code x_Trans_ID/);
210
211     $post_data{'x_Test_Request'} = $self->test_transaction() ? 'TRUE' : 'FALSE';
212
213     #deal with perl-style bool
214     if (    $post_data{'x_Email_Customer'}
215          && $post_data{'x_Email_Customer'} !~ /^FALSE$/i ) {
216       $post_data{'x_Email_Customer'} = 'TRUE';
217     } else {
218       $post_data{'x_Email_Customer'} = 'FALSE';
219     }
220
221     $post_data{'x_ADC_Delim_Data'} = 'TRUE';
222     $post_data{'x_delim_char'} = ',';
223     $post_data{'x_encap_char'} = '"';
224     $post_data{'x_ADC_URL'} = 'FALSE';
225     $post_data{'x_Version'} = '3.1';
226
227     my $pd = make_form(%post_data);
228     my $s = $self->server();
229     my $p = $self->port();
230     my $t = $self->path();
231     my $r = $self->{_content}->{referer};
232     my($page,$server_response,%headers) = post_https($s,$p,$t,$r,$pd);
233     #escape NULL (binary 0x00) values
234     $page =~ s/\x00/\^0/g;
235
236     #trim 'ip_addr="1.2.3.4"' added by eProcessingNetwork Authorize.Net compat
237     $page =~ s/,ip_addr="[\d\.]+"$//;
238
239     my $csv = new Text::CSV_XS({ binary=>1, escape_char=>'' });
240     $csv->parse($page);
241     my @col = $csv->fields();
242
243     $self->server_response($page);
244     $self->avs_code($col[5]);
245     $self->order_number($col[6]);
246     $self->md5($col[37]);
247     $self->cvv2_response($col[38]);
248     $self->cavv_response($col[39]);
249
250     if($col[0] eq "1" ) { # Authorized/Pending/Test
251         $self->is_success(1);
252         $self->result_code($col[0]);
253         if ($col[4] =~ /^(.*)\s+(\d+)$/) { #eProcessingNetwork extra bits..
254           $self->authorization($2);
255         } else {
256           $self->authorization($col[4]);
257         }
258     } else {
259         $self->is_success(0);
260         $self->result_code($col[2]);
261         $self->error_message($col[3]);
262         unless ( $self->result_code() ) { #additional logging information
263           #$page =~ s/\x00/\^0/g;
264           $self->error_message($col[3].
265             " DEBUG: No x_response_code from server, ".
266             "(HTTPS response: $server_response) ".
267             "(HTTPS headers: ".
268               join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
269             "(Raw HTTPS content: $page)"
270           );
271         }
272     }
273 }
274
275 1;
276 __END__
277
278 =head1 NAME
279
280 Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::OnlinePayment
281
282 =head1 SYNOPSIS
283
284   use Business::OnlinePayment;
285
286   ####
287   # One step transaction, the simple case.
288   ####
289
290   my $tx = new Business::OnlinePayment("AuthorizeNet");
291   $tx->content(
292       type           => 'VISA',
293       login          => 'testdrive',
294       password       => '', #password or transaction key
295       action         => 'Normal Authorization',
296       description    => 'Business::OnlinePayment test',
297       amount         => '49.95',
298       invoice_number => '100100',
299       customer_id    => 'jsk',
300       first_name     => 'Jason',
301       last_name      => 'Kohles',
302       address        => '123 Anystreet',
303       city           => 'Anywhere',
304       state          => 'UT',
305       zip            => '84058',
306       card_number    => '4007000000027',
307       expiration     => '09/02',
308       cvv2           => '1234', #optional
309       referer        => 'http://valid.referer.url/',
310   );
311   $tx->submit();
312
313   if($tx->is_success()) {
314       print "Card processed successfully: ".$tx->authorization."\n";
315   } else {
316       print "Card was rejected: ".$tx->error_message."\n";
317   }
318
319   ####
320   # Two step transaction, authorization and capture.
321   # If you don't need to review order before capture, you can
322   # process in one step as above.
323   ####
324
325   my $tx = new Business::OnlinePayment("AuthorizeNet");
326   $tx->content(
327       type           => 'VISA',
328       login          => 'testdrive',
329       password       => '',  #password or transaction key
330       action         => 'Authorization Only',
331       description    => 'Business::OnlinePayment test',
332       amount         => '49.95',
333       invoice_number => '100100',
334       customer_id    => 'jsk',
335       first_name     => 'Jason',
336       last_name      => 'Kohles',
337       address        => '123 Anystreet',
338       city           => 'Anywhere',
339       state          => 'UT',
340       zip            => '84058',
341       card_number    => '4007000000027',
342       expiration     => '09/02',
343       cvv2           => '1234', #optional
344       referer        => 'http://valid.referer.url/',
345   );
346   $tx->submit();
347
348   if($tx->is_success()) {
349       # get information about authorization
350       $authorization = $tx->authorization
351       $ordernum = $tx->order_number;
352       $avs_code = $tx->avs_code; # AVS Response Code
353       $cvv2_response = $tx->cvv2_response; # CVV2/CVC2/CID Response Code
354       $cavv_response = $tx->cavv_response; # Cardholder Authentication
355                                            # Verification Value (CAVV) Response
356                                            # Code
357
358       # now capture transaction
359       my $capture = new Business::OnlinePayment("AuthorizeNet");
360
361       $capture->content(
362           type           => 'CC',
363           action         => 'Post Authorization',
364           login          => 'YOURLOGIN
365           password       => 'YOURPASSWORD',
366           order_number   => $ordernum,
367           amount         => '49.95',
368       );
369
370       $capture->submit();
371
372       if($capture->is_success()) { 
373           print "Card captured successfully: ".$capture->authorization."\n";
374       } else {
375           print "Card was rejected: ".$capture->error_message."\n";
376       }
377
378   } else {
379       print "Card was rejected: ".$tx->error_message."\n";
380   }
381
382 =head1 SUPPORTED TRANSACTION TYPES
383
384 =head2 CC, Visa, MasterCard, American Express, Discover
385
386 Content required: type, login, password|transaction_key, action, amount, first_name, last_name, card_number, expiration.
387
388 =head2 Check
389
390 Content required: type, login, password|transaction_key, action, amount, first_name, last_name, account_number, routing_code, bank_name.
391
392 =head1 DESCRIPTION
393
394 For detailed information see L<Business::OnlinePayment>.
395
396 =head1 METHODS AND FUNCTIONS
397
398 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.
399
400 =head2 result_code
401
402 Returns the response reason code (this is different than the response code).
403
404 =head2 error_message
405
406 Returns the response reason text.
407
408 =head2 server_response
409
410 Returns the complete response from the server.
411
412 =head1 NOTE
413
414 Unlike Business::OnlinePayment or pre-3.0 verisons of
415 Business::OnlinePayment::AuthorizeNet, 3.1 requires separate first_name and
416 last_name fields.
417
418 Business::OnlinePayment::AuthorizeNet uses Authorize.Net's "Advanced
419 Integration Method (AIM) (formerly known as ADC direct response)", sending a
420 username and transaction_key or password with every transaction.  Therefore, Authorize.Net's
421 referrer "security" is not necessary.  In your Authorize.Net interface at
422 https://secure.authorize.net/ make sure the list of allowable referers is
423 blank.  Alternatively, set the B<referer> field in the transaction content.
424
425 To settle an authorization-only transaction (where you set action to
426 'Authorization Only'), submit the nine-digit transaction id code in
427 the field "order_number" with the action set to "Post Authorization".
428 You can get the transaction id from the authorization by calling the
429 order_number method on the object returned from the authorization.
430 You must also submit the amount field with a value less than or equal
431 to the amount specified in the original authorization.
432
433 Recently (February 2002), Authorize.Net has turned address
434 verification on by default for all merchants.  If you do not have
435 valid address information for your customer (such as in an IVR
436 application), you must disable address verification in the Merchant
437 Menu page at https://secure.authorize.net/ so that the transactions
438 aren't denied due to a lack of address information.
439
440 =head1 COMPATIBILITY
441
442 This module implements Authorize.Net's API verison 3.1 using the Advanced
443 Integration Method (AIM), formerly known as ADC Direct Response.  See
444 http://www.authorize.net/support/AIM_guide.pdf for details.
445
446 =head1 AUTHOR
447
448 Jason Kohles, jason@mediabang.com
449
450 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
451 3.0/3.1 and is the current maintainer.  Please send patches as unified diffs
452 (diff -u).
453
454 Jason Spence <jspence@lightconsulting.com> contributed support for separate
455 Authorization Only and Post Authorization steps and wrote some docs.
456 OST <services@ostel.com> paid for it.
457
458 T.J. Mather <tjmather@maxmind.com> sent a number of CVV2 patches.
459
460 Mike Barry <mbarry@cos.com> sent in a patch for the referer field.
461
462 Yuri V. Mkrtumyan <yuramk@novosoft.ru> sent in a patch to add the void action.
463
464 Paul Zimmer <AuthorizeNetpm@pzimmer.box.bepress.com> sent in a patch for
465 card-less post authorizations.
466
467 Daemmon Hughes <daemmon@daemmonhughes.com> sent in a patch for "transaction
468 key" authentication as well support for the recurring_billing flag and the md5
469 method that returns the MD5 hash which is returned by the gateway.
470
471 Steve Simitzis contributed a patch for better compatibility with
472 eProcessingNetwork's AuthorizeNet compatability mode.
473
474 =head1 SEE ALSO
475
476 perl(1). L<Business::OnlinePayment>.
477
478 =cut
479