File Coverage

blib/lib/Business/OnlinePayment/AuthorizeNet/AIM.pm
Criterion Covered Total %
statement 99 115 86.0
branch 24 44 54.5
condition 6 21 28.5
subroutine 12 12 100.0
pod 3 5 60.0
total 144 197 73.1


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::AuthorizeNet::AIM;
2              
3 3     3   16 use strict;
  3         6  
  3         703  
4 3     3   14 use Carp;
  3         4  
  3         169  
5 3     3   2657 use Business::OnlinePayment::HTTPS;
  3         87002  
  3         114  
6 3     3   31 use Business::OnlinePayment::AuthorizeNet;
  3         9  
  3         84  
7 3     3   2878 use Business::OnlinePayment::AuthorizeNet::AIM::ErrorCodes '%ERRORS';
  3         11  
  3         560  
8 3     3   4287 use Text::CSV_XS;
  3         31592  
  3         221  
9 3     3   32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  3         7  
  3         4596  
10              
11             @ISA = qw(Business::OnlinePayment::AuthorizeNet Business::OnlinePayment::HTTPS);
12             $VERSION = '3.22';
13              
14             sub set_defaults {
15 4     4 0 12 my $self = shift;
16              
17 4 50       145 $self->server('secure.authorize.net') unless $self->server;
18 4 50       184 $self->port('443') unless $self->port;
19 4 50       248 $self->path('/gateway/transact.dll') unless $self->path;
20              
21 4         167 $self->build_subs(qw( order_number md5 avs_code cvv2_response
22             cavv_response
23             ));
24             }
25              
26             sub map_fields {
27 4     4 0 6 my($self) = @_;
28              
29 4         32 my %content = $self->content();
30              
31             # ACTION MAP
32 4         133 my %actions = ('normal authorization' => 'AUTH_CAPTURE',
33             'authorization only' => 'AUTH_ONLY',
34             'credit' => 'CREDIT',
35             'post authorization' => 'PRIOR_AUTH_CAPTURE',
36             'void' => 'VOID',
37             );
38 4   33     31 $content{'action'} = $actions{lc($content{'action'} || '')} || $content{'action'};
39              
40             # TYPE MAP
41 4         25 my %types = ('visa' => 'CC',
42             'mastercard' => 'CC',
43             'american express' => 'CC',
44             'discover' => 'CC',
45             'check' => 'ECHECK',
46             );
47 4   33     26 $content{'type'} = $types{lc($content{'type'} || '')} || $content{'type'};
48 4         124 $self->transaction_type($content{'type'});
49              
50             # ACCOUNT TYPE MAP
51 4         47 my %account_types = ('personal checking' => 'CHECKING',
52             'personal savings' => 'SAVINGS',
53             'business checking' => 'CHECKING',
54             'business savings' => 'SAVINGS',
55             );
56 4   33     49 $content{'account_type'} = $account_types{lc($content{'account_type'} || '')}
57             || $content{'account_type'};
58              
59 4 50       21 if (length $content{'password'} == 15) {
60 0         0 $content{'transaction_key'} = delete $content{'password'};
61             }
62              
63             # stuff it back into %content
64 4         27 $self->content(%content);
65             }
66              
67             sub remap_fields {
68 4     4 1 141 my($self,%map) = @_;
69              
70 4         80 my %content = $self->content();
71 4         95 foreach(keys %map) {
72 228         477 $content{$map{$_}} = $content{$_};
73             }
74 4         65 $self->content(%content);
75             }
76              
77             sub get_fields {
78 4     4 1 30 my($self,@fields) = @_;
79              
80 4         16 my %content = $self->content();
81 4         152 my %new = ();
82 4         77 foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
  58         104  
83 4         75 return %new;
84             }
85              
86             sub submit {
87 4     4 1 9 my($self) = @_;
88              
89 4         16 $self->map_fields();
90 4         268 $self->remap_fields(
91             type => 'x_Method',
92             login => 'x_Login',
93             password => 'x_Password',
94             transaction_key => 'x_Tran_Key',
95             action => 'x_Type',
96             description => 'x_Description',
97             amount => 'x_Amount',
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',
108             city => 'x_City',
109             state => 'x_State',
110             zip => 'x_Zip',
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',
120             tax => 'x_Tax',
121             freight => 'x_Freight',
122             duty => 'x_Duty',
123             tax_exempt => 'x_Tax_Exempt',
124             po_number => 'x_Po_Num',
125             phone => 'x_Phone',
126             fax => 'x_Fax',
127             email => 'x_Email',
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',
148             );
149              
150 4 50       502 my $auth_type = $self->{_content}->{transaction_key}
151             ? 'transaction_key'
152             : 'password';
153              
154 4         12 my @required_fields = ( qw(type action login), $auth_type );
155              
156 4 50       15 unless ( $self->{_content}->{action} eq 'VOID' ) {
157              
158 4 50       102 if ($self->transaction_type() eq "ECHECK") {
    50          
159              
160 0         0 push @required_fields, qw(
161             amount routing_code account_number account_type bank_name
162             account_name
163             );
164              
165 0 0 0     0 if (defined $self->{_content}->{customer_org} and
166             length $self->{_content}->{customer_org}
167             ) {
168 0         0 push @required_fields, qw( customer_org customer_ssn );
169             } else {
170 0         0 push @required_fields, qw(license_num license_state license_dob);
171             }
172              
173             } elsif ($self->transaction_type() eq 'CC' ) {
174              
175 4 100       151 if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
    50          
176 1 50       5 if ( $self->{_content}->{order_number} ) {
177 1         4 push @required_fields, qw( amount order_number );
178             } else {
179 0         0 push @required_fields, qw( amount card_number expiration );
180             }
181             } elsif ( $self->{_content}->{action} eq 'CREDIT' ) {
182 0         0 push @required_fields, qw( amount order_number card_number );
183             } else {
184 3         12 push @required_fields, qw(
185             amount last_name first_name card_number expiration
186             );
187             }
188             } else {
189 0         0 Carp::croak( "AuthorizeNet can't handle transaction type: ".
190             $self->transaction_type() );
191             }
192              
193             }
194              
195 4         36 $self->required_fields(@required_fields);
196              
197 4         264 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
202             x_Bank_Check_Number
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
207             x_Country
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
210             x_Ship_To_Country
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/);
214              
215 4 100       130 $post_data{'x_Test_Request'} = $self->test_transaction() ? 'TRUE' : 'FALSE';
216              
217             #deal with perl-style bool
218 4 50 33     56 if ( $post_data{'x_Email_Customer'}
    50          
219             && $post_data{'x_Email_Customer'} !~ /^FALSE$/i ) {
220 0         0 $post_data{'x_Email_Customer'} = 'TRUE';
221             } elsif ( exists $post_data{'x_Email_Customer'} ) {
222 0         0 $post_data{'x_Email_Customer'} = 'FALSE';
223             }
224              
225 4         21 my $data_string = join("", values %post_data);
226              
227 4         7 my $encap_character;
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 4         9 foreach my $char( qw( | " ' : ; / \ - * ), '#', qw( ^ + < > [ ] ~), chr(31) ){
234 4 50       21 if( index($data_string, $char) == -1 ){ # found one.
235 4         6 $encap_character = $char;
236 4         8 last;
237             }
238             }
239              
240 4 50       13 if(!$encap_character){
241 0         0 $self->is_success(0);
242 0         0 $self->error_message(
243             "DEBUG: Input contains all encapsulating characters."
244             . " Please remove | or ^ from your input if possible."
245             );
246 0         0 return;
247             }
248              
249 4         9 $post_data{'x_ADC_Delim_Data'} = 'TRUE';
250 4         9 $post_data{'x_delim_char'} = ',';
251 4         7 $post_data{'x_encap_char'} = $encap_character;
252 4         7 $post_data{'x_ADC_URL'} = 'FALSE';
253 4         6 $post_data{'x_Version'} = '3.1';
254              
255 4 50       17 my $opt = defined( $self->{_content}->{referer} )
256             ? { 'headers' => { 'Referer' => $self->{_content}->{referer} } }
257             : {};
258              
259 4         65 my($page, $server_response, %headers) =
260             $self->https_post( $opt, \%post_data );
261              
262             #escape NULL (binary 0x00) values
263 4         3522866 $page =~ s/\x00/\^0/g;
264              
265             #trim 'ip_addr="1.2.3.4"' added by eProcessingNetwork Authorize.Net compat
266 4         19 $page =~ s/,ip_addr="[\d\.]+"$//;
267              
268 4         53 my $csv = new Text::CSV_XS({ binary=>1, escape_char=>'', quote_char => $encap_character });
269 4         672 $csv->parse($page);
270 4         625 my @col = $csv->fields();
271              
272 4         245 $self->server_response($page);
273 4         185 $self->avs_code($col[5]);
274 4         130 $self->order_number($col[6]);
275 4         120 $self->md5($col[37]);
276 4         178 $self->cvv2_response($col[38]);
277 4         140 $self->cavv_response($col[39]);
278              
279 4 100       83 if($col[0] eq "1" ) { # Authorized/Pending/Test
280 3         92 $self->is_success(1);
281 3         101 $self->result_code($col[0]);
282 3 50       36 if ($col[4] =~ /^(.*)\s+(\d+)$/) { #eProcessingNetwork extra bits..
283 0         0 $self->authorization($2);
284             } else {
285 3         120 $self->authorization($col[4]);
286             }
287             } else {
288 1         31 $self->is_success(0);
289 1         32 $self->result_code($col[2]);
290 1         63 $self->error_message($col[3]);
291 1 50       24 if ( $self->result_code ) {
292 1         24 my $addl = $ERRORS{ $self->result_code };
293 1 50 33     41 $self->error_message( $self->error_message. ' - '. $addl->{notes})
      33        
294             if $addl && ref($addl) eq 'HASH' && $addl->{notes};
295             } else { #additional logging information
296             #$page =~ s/\x00/\^0/g;
297 0           $self->error_message($col[3].
298             " DEBUG: No x_response_code from server, ".
299             "(HTTPS response: $server_response) ".
300             "(HTTPS headers: ".
301 0           join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
302             "(Raw HTTPS content: $page)"
303             );
304             }
305             }
306             }
307              
308             1;
309             __END__