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