1 package Business::OnlinePayment::AuthorizeNet::AIM;
5 use Business::OnlinePayment::HTTPS;
6 use Business::OnlinePayment::AuthorizeNet;
7 use Business::OnlinePayment::AuthorizeNet::AIM::ErrorCodes '%ERRORS';
9 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
11 @ISA = qw(Business::OnlinePayment::AuthorizeNet Business::OnlinePayment::HTTPS);
17 $self->server('secure.authorize.net') unless $self->server;
18 $self->port('443') unless $self->port;
19 $self->path('/gateway/transact.dll') unless $self->path;
21 $self->build_subs(qw( order_number md5 avs_code cvv2_response
29 my %content = $self->content();
32 my %actions = ('normal authorization' => 'AUTH_CAPTURE',
33 'authorization only' => 'AUTH_ONLY',
35 'post authorization' => 'PRIOR_AUTH_CAPTURE',
38 $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
41 my %types = ('visa' => 'CC',
43 'american express' => 'CC',
47 $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
48 $self->transaction_type($content{'type'});
51 my %account_types = ('personal checking' => 'CHECKING',
52 'personal savings' => 'SAVINGS',
53 'business checking' => 'CHECKING',
54 'business savings' => 'SAVINGS',
56 $content{'account_type'} = $account_types{lc($content{'account_type'})}
57 || $content{'account_type'};
59 if (length $content{'password'} == 15) {
60 $content{'transaction_key'} = delete $content{'password'};
63 # stuff it back into %content
64 $self->content(%content);
70 my %content = $self->content();
72 $content{$map{$_}} = $content{$_};
74 $self->content(%content);
78 my($self,@fields) = @_;
80 my %content = $self->content();
82 foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
93 password => 'x_Password',
94 transaction_key => 'x_Tran_Key',
96 description => 'x_Description',
98 currency => 'x_Currency_Code',
99 invoice_number => 'x_Invoice_Num',
100 order_number => 'x_Trans_ID',
101 auth_code => 'x_Auth_Code',
102 customer_id => 'x_Cust_ID',
103 customer_ip => 'x_Customer_IP',
104 last_name => 'x_Last_Name',
105 first_name => 'x_First_Name',
106 company => 'x_Company',
107 address => 'x_Address',
111 country => 'x_Country',
112 ship_last_name => 'x_Ship_To_Last_Name',
113 ship_first_name => 'x_Ship_To_First_Name',
114 ship_company => 'x_Ship_To_Company',
115 ship_address => 'x_Ship_To_Address',
116 ship_city => 'x_Ship_To_City',
117 ship_state => 'x_Ship_To_State',
118 ship_zip => 'x_Ship_To_Zip',
119 ship_country => 'x_Ship_To_Country',
121 freight => 'x_Freight',
123 tax_exempt => 'x_Tax_Exempt',
124 po_number => 'x_Po_Num',
128 email_customer => 'x_Email_Customer',
129 card_number => 'x_Card_Num',
130 expiration => 'x_Exp_Date',
131 cvv2 => 'x_Card_Code',
132 check_type => 'x_Echeck_Type',
133 account_name => 'x_Bank_Acct_Name',
134 account_number => 'x_Bank_Acct_Num',
135 account_type => 'x_Bank_Acct_Type',
136 bank_name => 'x_Bank_Name',
137 routing_code => 'x_Bank_ABA_Code',
138 check_number => 'x_Bank_Check_Number',
139 customer_org => 'x_Customer_Organization_Type',
140 customer_ssn => 'x_Customer_Tax_ID',
141 license_num => 'x_Drivers_License_Num',
142 license_state => 'x_Drivers_License_State',
143 license_dob => 'x_Drivers_License_DOB',
144 recurring_billing => 'x_Recurring_Billing',
145 duplicate_window => 'x_Duplicate_Window',
146 track1 => 'x_Track1',
147 track2 => 'x_Track2',
150 my $auth_type = $self->{_content}->{transaction_key}
154 my @required_fields = ( qw(type action login), $auth_type );
156 unless ( $self->{_content}->{action} eq 'VOID' ) {
158 if ($self->transaction_type() eq "ECHECK") {
160 push @required_fields, qw(
161 amount routing_code account_number account_type bank_name
165 if (defined $self->{_content}->{customer_org} and
166 length $self->{_content}->{customer_org}
168 push @required_fields, qw( customer_org customer_ssn );
170 push @required_fields, qw(license_num license_state license_dob);
173 } elsif ($self->transaction_type() eq 'CC' ) {
175 if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
176 if ( $self->{_content}->{order_number} ) {
177 push @required_fields, qw( amount order_number );
179 push @required_fields, qw( amount card_number expiration );
181 } elsif ( $self->{_content}->{action} eq 'CREDIT' ) {
182 push @required_fields, qw( amount order_number card_number );
184 push @required_fields, qw(
185 amount last_name first_name card_number expiration
189 Carp::croak( "AuthorizeNet can't handle transaction type: ".
190 $self->transaction_type() );
195 $self->required_fields(@required_fields);
197 my %post_data = $self->get_fields(qw/
198 x_Login x_Password x_Tran_Key x_Invoice_Num
199 x_Description x_Amount x_Cust_ID x_Method x_Type x_Card_Num x_Exp_Date
200 x_Card_Code x_Auth_Code x_Echeck_Type x_Bank_Acct_Num
201 x_Bank_Account_Name x_Bank_ABA_Code x_Bank_Name x_Bank_Acct_Type
203 x_Customer_Organization_Type x_Customer_Tax_ID x_Customer_IP
204 x_Drivers_License_Num x_Drivers_License_State x_Drivers_License_DOB
205 x_Last_Name x_First_Name x_Company
206 x_Address x_City x_State x_Zip
208 x_Ship_To_Last_Name x_Ship_To_First_Name x_Ship_To_Company
209 x_Ship_To_Address x_Ship_To_City x_Ship_To_State x_Ship_To_Zip
211 x_Tax x_Freight x_Duty x_Tax_Exempt x_Po_Num
212 x_Phone x_Fax x_Email x_Email_Customer x_Country
213 x_Currency_Code x_Trans_ID x_Duplicate_Window x_Track1 x_Track2/);
215 $post_data{'x_Test_Request'} = $self->test_transaction() ? 'TRUE' : 'FALSE';
217 #deal with perl-style bool
218 if ( $post_data{'x_Email_Customer'}
219 && $post_data{'x_Email_Customer'} !~ /^FALSE$/i ) {
220 $post_data{'x_Email_Customer'} = 'TRUE';
221 } elsif ( exists $post_data{'x_Email_Customer'} ) {
222 $post_data{'x_Email_Customer'} = 'FALSE';
225 my $data_string = join("", values %post_data);
228 # The first set of characters here are recommended by authorize.net in their
229 # encapsulating character example.
230 # The second set we made up hoping they will work if the first fail.
231 # The third chr(31) is the binary 'unit separator' and is our final last
232 # ditch effort to find something not in the input.
233 foreach my $char( qw( | " ' : ; / \ - * ), '#', qw( ^ + < > [ ] ~), chr(31) ){
234 if( index($data_string, $char) == -1 ){ # found one.
235 $encap_character = $char;
240 if(!$encap_character){
241 $self->is_success(0);
242 $self->error_message(
243 "DEBUG: Input contains all encapsulating characters."
244 . " Please remove | or ^ from your input if possible."
249 $post_data{'x_ADC_Delim_Data'} = 'TRUE';
250 $post_data{'x_delim_char'} = ',';
251 $post_data{'x_encap_char'} = $encap_character;
252 $post_data{'x_ADC_URL'} = 'FALSE';
253 $post_data{'x_Version'} = '3.1';
255 my $opt = defined( $self->{_content}->{referer} )
256 ? { 'headers' => { 'Referer' => $self->{_content}->{referer} } }
259 my($page, $server_response, %headers) =
260 $self->https_post( $opt, \%post_data );
262 #escape NULL (binary 0x00) values
263 $page =~ s/\x00/\^0/g;
265 #trim 'ip_addr="1.2.3.4"' added by eProcessingNetwork Authorize.Net compat
266 $page =~ s/,ip_addr="[\d\.]+"$//;
268 my $csv = new Text::CSV_XS({ binary=>1, escape_char=>'', quote_char => $encap_character });
270 my @col = $csv->fields();
272 $self->server_response($page);
273 $self->avs_code($col[5]);
274 $self->order_number($col[6]);
275 $self->md5($col[37]);
276 $self->cvv2_response($col[38]);
277 $self->cavv_response($col[39]);
279 if($col[0] eq "1" ) { # Authorized/Pending/Test
280 $self->is_success(1);
281 $self->result_code($col[0]);
282 if ($col[4] =~ /^(.*)\s+(\d+)$/) { #eProcessingNetwork extra bits..
283 $self->authorization($2);
285 $self->authorization($col[4]);
288 $self->is_success(0);
289 $self->result_code($col[2]);
290 $self->error_message($col[3]);
291 if ( $self->result_code ) {
292 my $addl = $ERRORS{ $self->result_code };
293 $self->error_message( $self->error_message. ' - '. $addl->{notes})
294 if $addl && ref($addl) eq 'HASH' && $addl->{notes};
295 } else { #additional logging information
296 #$page =~ s/\x00/\^0/g;
297 $self->error_message($col[3].
298 " DEBUG: No x_response_code from server, ".
299 "(HTTPS response: $server_response) ".
301 join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
302 "(Raw HTTPS content: $page)"
313 Business::OnlinePayment::AuthorizeNet::AIM - AuthorizeNet AIM backend for Business::OnlinePayment
317 Jason Kohles, jason@mediabang.com
319 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
320 3.0/3.1 and is the current maintainer. Please send patches as unified diffs
323 Jason Spence <jspence@lightconsulting.com> contributed support for separate
324 Authorization Only and Post Authorization steps and wrote some docs.
325 OST <services@ostel.com> paid for it.
327 T.J. Mather <tjmather@maxmind.com> sent a number of CVV2 patches.
329 Mike Barry <mbarry@cos.com> sent in a patch for the referer field.
331 Yuri V. Mkrtumyan <yuramk@novosoft.ru> sent in a patch to add the void action.
333 Paul Zimmer <AuthorizeNetpm@pzimmer.box.bepress.com> sent in a patch for
334 card-less post authorizations.
336 Daemmon Hughes <daemmon@daemmonhughes.com> sent in a patch for "transaction
337 key" authentication as well support for the recurring_billing flag and the md5
338 method that returns the MD5 hash which is returned by the gateway.
340 Steve Simitzis contributed a patch for better compatibility with
341 eProcessingNetwork's AuthorizeNet compatability mode.
345 perl(1). L<Business::OnlinePayment> L<Business::OnlinePayment::AuthorizeNet>.