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