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