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