From Daemon Hughes <daemmon@daemmonhughes.com>:
[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.13';
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('order_number'); #no idea how it worked for jason w/o this
25     $self->build_subs('md5');
26 }
27
28 sub map_fields {
29     my($self) = @_;
30
31     my %content = $self->content();
32
33     # ACTION MAP
34     my %actions = ('normal authorization' => 'AUTH_CAPTURE',
35                    'authorization only'   => 'AUTH_ONLY',
36                    'credit'               => 'CREDIT',
37                    'post authorization'   => 'PRIOR_AUTH_CAPTURE',
38                    'void'                 => 'VOID',
39                   );
40     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
41
42     # TYPE MAP
43     my %types = ('visa'               => 'CC',
44                  'mastercard'         => 'CC',
45                  'american express'   => 'CC',
46                  'discover'           => 'CC',
47                  'check'              => 'ECHECK',
48                 );
49     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
50     $self->transaction_type($content{'type'});
51
52     $content{'referer'} = defined( $content{'referer'} )
53                             ? make_headers( 'Referer' => $content{'referer'} )
54                             : "";
55
56     # stuff it back into %content
57     $self->content(%content);
58 }
59
60 sub remap_fields {
61     my($self,%map) = @_;
62
63     my %content = $self->content();
64     foreach(keys %map) {
65         $content{$map{$_}} = $content{$_};
66     }
67     $self->content(%content);
68 }
69
70 sub get_fields {
71     my($self,@fields) = @_;
72
73     my %content = $self->content();
74     my %new = ();
75     foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
76     return %new;
77 }
78
79 sub submit {
80     my($self) = @_;
81
82     $self->map_fields();
83     $self->remap_fields(
84         type           => 'x_Method',
85         login          => 'x_Login',
86         password       => 'x_Password',
87         transaction_key       => 'x_Tran_Key',
88         action         => 'x_Type',
89         description    => 'x_Description',
90         amount         => 'x_Amount',
91         currency       => 'x_Currency_Code',
92         invoice_number => 'x_Invoice_Num',
93         order_number   => 'x_Trans_ID',
94         auth_code      => 'x_Auth_Code',
95         customer_id    => 'x_Cust_ID',
96         customer_ip    => 'x_Customer_IP',
97         last_name      => 'x_Last_Name',
98         first_name     => 'x_First_Name',
99         address        => 'x_Address',
100         city           => 'x_City',
101         state          => 'x_State',
102         zip            => 'x_Zip',
103         country        => 'x_Country',
104         phone          => 'x_Phone',
105         fax            => 'x_Fax',
106         email          => 'x_Email',
107         company        => 'x_Company',
108         card_number    => 'x_Card_Num',
109         expiration     => 'x_Exp_Date',
110         cvv2           => 'x_Card_Code',
111         check_type     => 'x_Echeck_Type',
112         account_name   => 'x_Bank_Acct_Name',
113         account_number => 'x_Bank_Acct_Num',
114         account_type   => 'x_Bank_Acct_Type',
115         bank_name      => 'x_Bank_Name',
116         routing_code   => 'x_Bank_ABA_Code',
117         customer_org   => 'x_Customer_Organization_Type', 
118         customer_ssn   => 'x_Customer_Tax_ID',
119         license_num    => 'x_Drivers_License_Num',
120         license_state  => 'x_Drivers_License_State',
121         license_dob    => 'x_Drivers_License_DOB',
122         recurring_billing    => 'x_Recurring_Billing',
123     );
124     my $auth_type = $self->{_content}->{transaction_key}?'transaction_key':'password';
125
126     if ($self->transaction_type() eq "ECHECK") {
127         if ($self->{_content}->{customer_org} ne '') {
128             $self->required_fields(qw/type login amount routing_code
129                                   account_number account_type bank_name
130                                   account_name account_type
131                                   customer_org customer_ssn/, $auth_type);
132         } else {
133             $self->required_fields(qw/type login amount routing_code
134                                   account_number account_type bank_name
135                                   account_name account_type
136                                   license_num license_state license_dob/, $auth_type);
137         }
138     } elsif ($self->transaction_type() eq 'CC' ) {
139       if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
140           if ( $self->{_content}->{order_number}) {
141               $self->required_fields(qw/type login action amount/, $auth_type);
142           } else {
143               $self->required_fields(qw/type login action amount 
144                                         card_number expiration/, $auth_type);
145           }
146       } elsif ( $self->{_content}->{action} eq 'VOID' ) {
147         $self->required_fields(qw/login action/,$auth_type);
148       } else {
149         $self->required_fields(qw/type login action amount last_name
150                                   first_name card_number expiration/, $auth_type);
151       }
152     } else {
153         Carp::croak("AuthorizeNet can't handle transaction type: ".
154                     $self->transaction_type());
155     }
156
157     my %post_data = $self->get_fields(qw/x_Login x_Password x_Tran_Key x_Invoice_Num
158         x_Description x_Amount x_Cust_ID x_Method x_Type x_Card_Num x_Exp_Date
159         x_Card_Code x_Auth_Code x_Echeck_Type x_Bank_Acct_Num
160         x_Bank_Account_Name x_Bank_ABA_Code x_Bank_Name x_Bank_Acct_Type
161         x_Customer_Organization_Type x_Customer_Tax_ID x_Customer_IP
162         x_Drivers_License_Num x_Drivers_License_State x_Drivers_License_DOB
163         x_Last_Name x_First_Name x_Address x_City x_State x_Zip x_Country
164         x_Phone x_Fax x_Email x_Email_Customer x_Company x_Country
165         x_Currency_Code x_Trans_ID/);
166     $post_data{'x_Test_Request'} = $self->test_transaction()?"TRUE":"FALSE";
167     $post_data{'x_ADC_Delim_Data'} = 'TRUE';
168     $post_data{'x_ADC_URL'} = 'FALSE';
169     $post_data{'x_Version'} = '3.1';
170
171     my $pd = make_form(%post_data);
172     my $s = $self->server();
173     my $p = $self->port();
174     my $t = $self->path();
175     my $r = $self->{_content}->{referer};
176     my($page,$server_response,%headers) = post_https($s,$p,$t,$r,$pd);
177     #escape NULL (binary 0x00) values
178     $page =~ s/\x00/\^0/g;
179
180     my $csv = new Text::CSV_XS({ 'binary'=>1 });
181     $csv->parse($page);
182     my @col = $csv->fields();
183
184     $self->server_response($page);
185     $self->md5($col[37]);
186     if($col[0] eq "1" ) { # Authorized/Pending/Test
187         $self->is_success(1);
188         $self->result_code($col[0]);
189         $self->authorization($col[4]);
190         $self->order_number($col[6]);
191     } else {
192         $self->is_success(0);
193         $self->result_code($col[2]);
194         $self->error_message($col[3]);
195         unless ( $self->result_code() ) { #additional logging information
196           #$page =~ s/\x00/\^0/g;
197           $self->error_message($col[3].
198             " DEBUG: No x_response_code from server, ".
199             "(HTTPS response: $server_response) ".
200             "(HTTPS headers: ".
201               join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
202             "(Raw HTTPS content: $page)"
203           );
204         }
205     }
206 }
207
208 1;
209 __END__
210
211 =head1 NAME
212
213 Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::OnlinePayment
214
215 =head1 SYNOPSIS
216
217   use Business::OnlinePayment;
218
219   my $tx = new Business::OnlinePayment("AuthorizeNet");
220   $tx->content(
221       type           => 'VISA',
222       login          => 'testdrive',
223       password       => '',
224       action         => 'Normal Authorization',
225       description    => 'Business::OnlinePayment test',
226       amount         => '49.95',
227       invoice_number => '100100',
228       customer_id    => 'jsk',
229       first_name     => 'Jason',
230       last_name      => 'Kohles',
231       address        => '123 Anystreet',
232       city           => 'Anywhere',
233       state          => 'UT',
234       zip            => '84058',
235       card_number    => '4007000000027',
236       expiration     => '09/02',
237       cvv2           => '1234', #optional
238       referer        => 'http://valid.referer.url/',
239   );
240   $tx->submit();
241
242   if($tx->is_success()) {
243       print "Card processed successfully: ".$tx->authorization."\n";
244   } else {
245       print "Card was rejected: ".$tx->error_message."\n";
246   }
247
248 =head1 SUPPORTED TRANSACTION TYPES
249
250 =head2 Visa, MasterCard, American Express, Discover
251
252 Content required: type, login, password|transaction_key, action, amount, first_name, last_name, card_number, expiration.
253
254 =head2 Check
255
256 Content required: type, login, password|transaction_key, action, amount, first_name, last_name, account_number, routing_code, bank_name.
257
258 =head1 DESCRIPTION
259
260 For detailed information see L<Business::OnlinePayment>.
261
262 =head1 NOTE
263
264 Unlike Business::OnlinePayment or pre-3.0 verisons of
265 Business::OnlinePayment::AuthorizeNet, 3.1 requires separate first_name and
266 last_name fields.
267
268 Business::OnlinePayment::AuthorizeNet uses Authorize.Net's "Advanced
269 Integration Method (AIM) (formerly known as ADC direct response)", sending a
270 username and transaction_key or password with every transaction.  Therefore, Authorize.Net's
271 referrer "security" is not necessary.  In your Authorize.Net interface at
272 https://secure.authorize.net/ make sure the list of allowable referers is
273 blank.  Alternatively, set the B<referer> field in the transaction content.
274
275 To settle an authorization-only transaction (where you set action to
276 'Authorization Only'), submit the nine-digit transaction id code in
277 the field "order_number" with the action set to "Post Authorization".
278 You can get the transaction id from the authorization by calling the
279 order_number method on the object returned from the authorization.
280 You must also submit the amount field with a value less than or equal
281 to the amount specified in the original authorization.
282
283 Recently (February 2002), Authorize.Net has turned address
284 verification on by default for all merchants.  If you do not have
285 valid address information for your customer (such as in an IVR
286 application), you must disable address verification in the Merchant
287 Menu page at https://secure.authorize.net/ so that the transactions
288 aren't denied due to a lack of address information.
289
290 =head1 COMPATIBILITY
291
292 This module implements Authorize.Net's API verison 3.1 using the ADC
293 Direct Response method.  See
294 https://secure.authorize.net/docs/developersguide.pml for details.
295
296 =head1 AUTHOR
297
298 Jason Kohles, jason@mediabang.com
299
300 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
301 3.0/3.1 and is the current maintainer.  Please send patches as unified diffs
302 (diff -u).
303
304 Jason Spence <jspence@lightconsulting.com> contributed support for separate
305 Authorization Only and Post Authorization steps and wrote some docs.
306 OST <services@ostel.com> paid for it.
307
308 T.J. Mather <tjmather@maxmind.com> sent a patch for the CVV2 field.
309
310 Mike Barry <mbarry@cos.com> sent in a patch for the referer field.
311
312 Yuri V. Mkrtumyan <yuramk@novosoft.ru> sent in a patch to add the void action.
313
314 Paul Zimmer <AuthorizeNetpm@pzimmer.box.bepress.com> sent in a patch for
315 card-less post authorizations.
316
317 Daemmon Hughes <daemmon@daemmonhughes.com> sent in a patch for "transaction
318 key" authentication as well support for the recurring_billing flag and the md5'
319 method that returns the MD5 hash which is returned by the gateway.
320
321 =head1 SEE ALSO
322
323 perl(1). L<Business::OnlinePayment>.
324
325 =cut
326