63e2b6fb97f9aa444e62f891d4a4b05fb5bcf042
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet.pm
1 package Business::OnlinePayment::AuthorizeNet;
2
3 # $Id: AuthorizeNet.pm,v 1.13 2002-08-14 00:09:11 ivan Exp $
4
5 use strict;
6 use Carp;
7 use Business::OnlinePayment;
8 use Net::SSLeay qw/make_form post_https make_headers/;
9 use Text::CSV_XS;
10 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
11
12 require Exporter;
13
14 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
15 @EXPORT = qw();
16 @EXPORT_OK = qw();
17 $VERSION = '3.12';
18
19 sub set_defaults {
20     my $self = shift;
21
22     $self->server('secure.authorize.net');
23     $self->port('443');
24     $self->path('/gateway/transact.dll');
25
26     $self->build_subs('order_number'); #no idea how it worked for jason w/o this
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                   );
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         action         => 'x_Type',
88         description    => 'x_Description',
89         amount         => 'x_Amount',
90         invoice_number => 'x_Invoice_Num',
91         customer_id    => 'x_Cust_ID',
92         last_name      => 'x_Last_Name',
93         first_name     => 'x_First_Name',
94         address        => 'x_Address',
95         city           => 'x_City',
96         state          => 'x_State',
97         zip            => 'x_Zip',
98         card_number    => 'x_Card_Num',
99         expiration     => 'x_Exp_Date',
100         account_number => 'x_Bank_Acct_Num',
101         routing_code   => 'x_Bank_ABA_Code',
102         bank_name      => 'x_Bank_Name',
103         country        => 'x_Country',
104         phone          => 'x_Phone',
105         fax            => 'x_Fax',
106         email          => 'x_Email',
107         company        => 'x_Company',
108         order_number   => 'x_Trans_ID',
109         cvv2           => 'x_Card_Code',
110         currency       => 'x_Currency_Code',
111     );
112
113     if($self->transaction_type() eq "ECHECK") {
114         $self->required_fields(qw/type login password action amount last_name
115                                   first_name account_number routing_code
116                                   bank_name/);
117     } elsif($self->transaction_type() eq 'CC' ) {
118       if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
119         $self->required_fields(qw/type login password action amount
120                                   card_number expiration/);
121       } else {
122         $self->required_fields(qw/type login password action amount last_name
123                                   first_name card_number expiration/);
124       }
125     } else {
126         Carp::croak("AuthorizeNet can't handle transaction type: ".
127                     $self->transaction_type());
128     }
129
130     my %post_data = $self->get_fields(qw/x_Login x_Password x_Invoice_Num
131                                          x_Description x_Amount x_Cust_ID
132                                          x_Method x_Type x_Card_Num x_Exp_Date
133                                          x_Auth_Code x_Bank_Acct_Num
134                                          x_Bank_ABA_Code x_Bank_Name
135                                          x_Last_Name x_First_Name x_Address
136                                          x_City x_State x_Zip x_Country x_Phone
137                                          x_Fax x_Email x_Email_Customer
138                                          x_Company x_Country x_Trans_ID
139                                          x_Card_Code /); 
140     $post_data{'x_Test_Request'} = $self->test_transaction()?"TRUE":"FALSE";
141     $post_data{'x_ADC_Delim_Data'} = 'TRUE';
142     $post_data{'x_ADC_URL'} = 'FALSE';
143     $post_data{'x_Version'} = '3.1';
144
145     my $pd = make_form(%post_data);
146     my $s = $self->server();
147     my $p = $self->port();
148     my $t = $self->path();
149     my $r = $self->{_content}->{referer};
150     my($page,$server_response,%headers) = post_https($s,$p,$t,$r,$pd);
151     #escape NULL (binary 0x00) values
152     $page =~ s/\x00/\^0/g;
153
154     my $csv = new Text::CSV_XS();
155     $csv->parse($page);
156     my @col = $csv->fields();
157
158     $self->server_response($page);
159     if($col[0] eq "1" ) { # Authorized/Pending/Test
160         $self->is_success(1);
161         $self->result_code($col[0]);
162         $self->authorization($col[4]);
163         $self->order_number($col[6]);
164     } else {
165         $self->is_success(0);
166         $self->result_code($col[2]);
167         $self->error_message($col[3]);
168         unless ( $self->result_code() ) { #additional logging information
169           #$page =~ s/\x00/\^0/g;
170           $self->error_message($col[3].
171             " DEBUG: No x_response_code from server, ".
172             "(HTTPS response: $server_response) ".
173             "(HTTPS headers: ".
174               join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
175             "(Raw HTTPS content: $page)"
176           );
177         }
178     }
179 }
180
181 1;
182 __END__
183
184 =head1 NAME
185
186 Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::OnlinePayment
187
188 =head1 SYNOPSIS
189
190   use Business::OnlinePayment;
191
192   my $tx = new Business::OnlinePayment("AuthorizeNet");
193   $tx->content(
194       type           => 'VISA',
195       login          => 'testdrive',
196       password       => '',
197       action         => 'Normal Authorization',
198       description    => 'Business::OnlinePayment test',
199       amount         => '49.95',
200       invoice_number => '100100',
201       customer_id    => 'jsk',
202       first_name     => 'Jason',
203       last_name      => 'Kohles',
204       address        => '123 Anystreet',
205       city           => 'Anywhere',
206       state          => 'UT',
207       zip            => '84058',
208       card_number    => '4007000000027',
209       expiration     => '09/02',
210       cvv2           => '1234', #optional
211       referer        => 'http://valid.referer.url/',
212   );
213   $tx->submit();
214
215   if($tx->is_success()) {
216       print "Card processed successfully: ".$tx->authorization."\n";
217   } else {
218       print "Card was rejected: ".$tx->error_message."\n";
219   }
220
221 =head1 SUPPORTED TRANSACTION TYPES
222
223 =head2 Visa, MasterCard, American Express, Discover
224
225 Content required: type, login, password, action, amount, first_name, last_name, card_number, expiration.
226
227 =head2 Check
228
229 Content required: type, login, password, action, amount, first_name, last_name, account_number, routing_code, bank_name.
230
231 =head1 DESCRIPTION
232
233 For detailed information see L<Business::OnlinePayment>.
234
235 =head1 NOTE
236
237 Unlike Business::OnlinePayment or pre-3.0 verisons of
238 Business::OnlinePayment::AuthorizeNet, 3.1 requires separate first_name and
239 last_name fields.
240
241 To settle an authorization-only transaction (where you set action to
242 'Authorization Only'), submit the nine-digit transaction id code in
243 the field "order_number" with the action set to "Post Authorization".
244 You can get the transaction id from the authorization by calling the
245 order_number method on the object returned from the authorization.
246 You must also submit the amount field with a value less than or equal
247 to the amount specified in the original authorization.
248
249 Recently (February 2002), Authorize.Net has turned address
250 verification on by default for all merchants.  If you do not have
251 valid address information for your customer (such as in an IVR
252 application), you must disable address verification in the Merchant
253 Menu page at https://secure.authorize.net/ so that the transactions
254 aren't denied due to a lack of address information.
255
256 =head1 COMPATIBILITY
257
258 This module implements Authorize.Net's API verison 3.1 using the ADC
259 Direct Response method.  See
260 https://secure.authorize.net/docs/developersguide.pml for details.
261
262 =head1 AUTHOR
263
264 Jason Kohles, jason@mediabang.com
265
266 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
267 3.0/3.1 and is the current maintainer.
268
269 Jason Spence <jspence@lightconsulting.com> contributed support for separate
270 Authorization Only and Post Authorization steps and wrote some docs.
271 OST <services@ostel.com> paid for it.
272
273 T.J. Mather <tjmather@maxmind.com> sent a patch for the CVV2 field.
274
275 Mike Barry <mbarry@cos.com> sent in a patch for the referer field.
276
277 =head1 SEE ALSO
278
279 perl(1). L<Business::OnlinePayment>.
280
281 =cut
282