escape nulls for testing
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet.pm
1 package Business::OnlinePayment::AuthorizeNet;
2
3 # $Id: AuthorizeNet.pm,v 1.9 2002-04-24 02:39:38 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
143     my $csv = new Text::CSV_XS();
144     $csv->parse($page);
145     my @col = $csv->fields();
146
147     $self->server_response($page);
148     if($col[0] eq "1" ) { # Authorized/Pending/Test
149         $self->is_success(1);
150         $self->result_code($col[0]);
151         $self->authorization($col[4]);
152         $self->order_number($col[6]);
153     } else {
154         $self->is_success(0);
155         $self->result_code($col[2]);
156         $self->error_message($col[3]);
157         unless ( $self->result_code() ) { #additional logging information
158           $page =~ s/\x00/\^0/g;
159           $self->error_message($col[3].
160             " DEBUG: No x_response_code from server, ".
161             "(HTTPS response: $server_response) ".
162             "(HTTPS headers: ".
163               join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
164             "(Raw HTTPS content: $page)"
165           );
166         }
167     }
168 }
169
170 1;
171 __END__
172
173 =head1 NAME
174
175 Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::OnlinePayment
176
177 =head1 SYNOPSIS
178
179   use Business::OnlinePayment;
180
181   my $tx = new Business::OnlinePayment("AuthorizeNet");
182   $tx->content(
183       type           => 'VISA',
184       login          => 'testdrive',
185       password       => '',
186       action         => 'Normal Authorization',
187       description    => 'Business::OnlinePayment test',
188       amount         => '49.95',
189       invoice_number => '100100',
190       customer_id    => 'jsk',
191       first_name     => 'Jason',
192       last_name      => 'Kohles',
193       address        => '123 Anystreet',
194       city           => 'Anywhere',
195       state          => 'UT',
196       zip            => '84058',
197       card_number    => '4007000000027',
198       expiration     => '09/02',
199   );
200   $tx->submit();
201
202   if($tx->is_success()) {
203       print "Card processed successfully: ".$tx->authorization."\n";
204   } else {
205       print "Card was rejected: ".$tx->error_message."\n";
206   }
207
208 =head1 SUPPORTED TRANSACTION TYPES
209
210 =head2 Visa, MasterCard, American Express, Discover
211
212 Content required: type, login, password, action, amount, first_name, last_name, card_number, expiration.
213
214 =head2 Check
215
216 Content required: type, login, password, action, amount, first_name, last_name, account_number, routing_code, bank_name.
217
218 =head1 DESCRIPTION
219
220 For detailed information see L<Business::OnlinePayment>.
221
222 =head1 NOTE
223
224 Unlike Business::OnlinePayment or pre-3.0 verisons of
225 Business::OnlinePayment::AuthorizeNet, 3.1 requires separate first_name and
226 last_name fields.
227
228 To settle an authorization-only transaction (where you set action to
229 'Authorization Only'), submit the nine-digit transaction id code in
230 the field "order_number" with the action set to "Post Authorization".
231 You can get the transaction id from the authorization by calling the
232 order_number method on the object returned from the authorization.
233 You must also submit the amount field with a value less than or equal
234 to the amount specified in the original authorization.
235
236 Recently (February 2002), Authorize.Net has turned address
237 verification on by default for all merchants.  If you do not have
238 valid address information for your customer (such as in an IVR
239 application), you must disable address verification in the Merchant
240 Menu page at https://secure.authorize.net/ so that the transactions
241 aren't denied due to a lack of address information.
242
243 =head1 COMPATIBILITY
244
245 This module implements Authorize.Net's API verison 3.1 using the ADC
246 Direct Response method.  See
247 https://secure.authorize.net/docs/developersguide.pml for details.
248
249 =head1 AUTHOR
250
251 Jason Kohles, jason@mediabang.com
252
253 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
254 3.0/3.1 and is the current maintainer.
255
256 Jason Spence <jspence@lightconsulting.com> contributed support for separate
257 Authorization Only and Post Authorization steps and wrote some docs.
258 OST <services@ostel.com> paid for it.
259
260 =head1 SEE ALSO
261
262 perl(1). L<Business::OnlinePayment>.
263
264 =cut
265