File Coverage

blib/lib/Business/OnlinePayment/AuthorizeNet/AIM.pm
Criterion Covered Total %
statement 99 115 86.0
branch 26 46 56.5
condition 6 24 25.0
subroutine 12 12 100.0
pod 3 5 60.0
total 146 202 72.2


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::AuthorizeNet::AIM;
2              
3 4     4   22 use strict;
  4         8  
  4         105  
4 4     4   19 use Carp;
  4         9  
  4         232  
5 4     4   2433 use Business::OnlinePayment::HTTPS;
  4         93038  
  4         137  
6 4     4   37 use Business::OnlinePayment::AuthorizeNet;
  4         9  
  4         114  
7 4     4   3872 use Business::OnlinePayment::AuthorizeNet::AIM::ErrorCodes '%ERRORS';
  4         16  
  4         794  
8 4     4   6162 use Text::CSV_XS;
  4         47645  
  4         289  
9 4     4   40 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  4         12  
  4         6206  
10              
11             @ISA = qw(Business::OnlinePayment::AuthorizeNet Business::OnlinePayment::HTTPS);
12             $VERSION = '3.23';
13              
14             sub set_defaults {
15 5     5 0 12 my $self = shift;
16              
17 5 50       175 $self->server('secure.authorize.net') unless $self->server;
18 5 100       237 $self->port('443') unless $self->port;
19 5 100       312 $self->path('/gateway/transact.dll') unless $self->path;
20              
21 5         194 $self->build_subs(qw( order_number md5 avs_code cvv2_response
22             cavv_response
23             ));
24             }
25              
26             sub map_fields {
27 5     5 0 11 my($self) = @_;
28              
29 5         41 my %content = $self->content();
30              
31             # ACTION MAP
32 5         165 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 5   33     41 $content{'action'} = $actions{lc($content{'action'} || '')} || $content{'action'};
39              
40             # TYPE MAP
41 5         32 my %types = ('visa' => 'CC',
42             'mastercard' => 'CC',
43             'american express' => 'CC',
44             'discover' => 'CC',
45             'check' => 'ECHECK',
46             );
47 5   33     34 $content{'type'} = $types{lc($content{'type'} || '')} || $content{'type'};
48 5         167 $self->transaction_type($content{'type'});
49              
50             # ACCOUNT TYPE MAP
51 5         62 my %account_types = ('personal checking' => 'CHECKING',
52             'personal savings' => 'SAVINGS',
53             'business checking' => 'CHECKING',
54             'business savings' => 'SAVINGS',
55             );
56             $content{'account_type'} = $account_types{lc($content{'account_type'} || '')}
57 5   33     54 || $content{'account_type'};
58              
59 5 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 5         38 $self->content(%content);
65             }
66              
67             sub remap_fields {
68 5     5 1 208 my($self,%map) = @_;
69              
70 5         21 my %content = $self->content();
71 5         132 foreach(keys %map) {
72 285         661 $content{$map{$_}} = $content{$_};
73             }
74 5         86 $self->content(%content);
75             }
76              
77             sub get_fields {
78 5     5 1 43 my($self,@fields) = @_;
79              
80 5         22 my %content = $self->content();
81 5         212 my %new = ();
82 5         105 foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
  68         157  
83 5         106 return %new;
84             }
85              
86             sub submit {
87 5     5 1 14 my($self) = @_;
88              
89 5         56 $self->map_fields();
90 5         354 $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             my $auth_type = $self->{_content}->{transaction_key}
151 5 50       674 ? 'transaction_key'
152             : 'password';
153              
154 5         18 my @required_fields = ( qw(type action login), $auth_type );
155              
156 5 50       24 unless ( $self->{_content}->{action} eq 'VOID' ) {
157              
158 5 50       133 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
    0 0        
166             length $self->{_content}->{customer_org}
167             ) {
168 0         0 push @required_fields, qw( customer_org customer_ssn );
169             }
170             elsif ( defined $self->{_content}->{license_num} and
171             length $self->{_content}->{license_num}
172             ) {
173 0         0 push @required_fields, qw(license_num license_state license_dob);
174             }
175              
176             } elsif ($self->transaction_type() eq 'CC' ) {
177              
178 5 100       207 if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
    50          
179 1 50       6 if ( $self->{_content}->{order_number} ) {
180 1         5 push @required_fields, qw( amount order_number );
181             } else {
182 0         0 push @required_fields, qw( amount card_number expiration );
183             }
184             } elsif ( $self->{_content}->{action} eq 'CREDIT' ) {
185 0         0 push @required_fields, qw( amount order_number card_number );
186             } else {
187 4         16 push @required_fields, qw(
188             amount last_name first_name card_number expiration
189             );
190             }
191             } else {
192 0         0 Carp::croak( "AuthorizeNet can't handle transaction type: ".
193             $self->transaction_type() );
194             }
195              
196             }
197              
198 5         44 $self->required_fields(@required_fields);
199              
200 5         381 my %post_data = $self->get_fields(qw/
201             x_Login x_Password x_Tran_Key x_Invoice_Num
202             x_Description x_Amount x_Cust_ID x_Method x_Type x_Card_Num x_Exp_Date
203             x_Card_Code x_Auth_Code x_Echeck_Type x_Bank_Acct_Num
204             x_Bank_Account_Name x_Bank_ABA_Code x_Bank_Name x_Bank_Acct_Type
205             x_Bank_Check_Number
206             x_Customer_Organization_Type x_Customer_Tax_ID x_Customer_IP
207             x_Drivers_License_Num x_Drivers_License_State x_Drivers_License_DOB
208             x_Last_Name x_First_Name x_Company
209             x_Address x_City x_State x_Zip
210             x_Country
211             x_Ship_To_Last_Name x_Ship_To_First_Name x_Ship_To_Company
212             x_Ship_To_Address x_Ship_To_City x_Ship_To_State x_Ship_To_Zip
213             x_Ship_To_Country
214             x_Tax x_Freight x_Duty x_Tax_Exempt x_Po_Num
215             x_Phone x_Fax x_Email x_Email_Customer x_Country
216             x_Currency_Code x_Trans_ID x_Duplicate_Window x_Track1 x_Track2/);
217              
218 5 100       175 $post_data{'x_Test_Request'} = $self->test_transaction() ? 'TRUE' : 'FALSE';
219              
220             #deal with perl-style bool
221 5 50 33     140 if ( $post_data{'x_Email_Customer'}
    50          
222             && $post_data{'x_Email_Customer'} !~ /^FALSE$/i ) {
223 0         0 $post_data{'x_Email_Customer'} = 'TRUE';
224             } elsif ( exists $post_data{'x_Email_Customer'} ) {
225 0         0 $post_data{'x_Email_Customer'} = 'FALSE';
226             }
227              
228 5         29 my $data_string = join("", values %post_data);
229              
230 5         10 my $encap_character;
231             # The first set of characters here are recommended by authorize.net in their
232             # encapsulating character example.
233             # The second set we made up hoping they will work if the first fail.
234             # The third chr(31) is the binary 'unit separator' and is our final last
235             # ditch effort to find something not in the input.
236 5         16 foreach my $char( qw( | " ' : ; / \ - * ), '#', qw( ^ + < > [ ] ~), chr(31) ){
237 5 50       31 if( index($data_string, $char) == -1 ){ # found one.
238 5         9 $encap_character = $char;
239 5         12 last;
240             }
241             }
242              
243 5 50       18 if(!$encap_character){
244 0         0 $self->is_success(0);
245 0         0 $self->error_message(
246             "DEBUG: Input contains all encapsulating characters."
247             . " Please remove | or ^ from your input if possible."
248             );
249 0         0 return;
250             }
251              
252 5         12 $post_data{'x_ADC_Delim_Data'} = 'TRUE';
253 5         11 $post_data{'x_delim_char'} = ',';
254 5         12 $post_data{'x_encap_char'} = $encap_character;
255 5         12 $post_data{'x_ADC_URL'} = 'FALSE';
256 5         12 $post_data{'x_Version'} = '3.1';
257              
258             my $opt = defined( $self->{_content}->{referer} )
259             ? { 'headers' => { 'Referer' => $self->{_content}->{referer} } }
260 5 50       22 : {};
261              
262 5         59 my($page, $server_response, %headers) =
263             $self->https_post( $opt, \%post_data );
264              
265             #escape NULL (binary 0x00) values
266 5         1323119 $page =~ s/\x00/\^0/g;
267              
268             #trim 'ip_addr="1.2.3.4"' added by eProcessingNetwork Authorize.Net compat
269 5         27 $page =~ s/,ip_addr="[\d\.]+"$//;
270              
271 5         100 my $csv = new Text::CSV_XS({ binary=>1, escape_char=>'', quote_char => $encap_character });
272 5         1393 $csv->parse($page);
273 5         617 my @col = $csv->fields();
274              
275 5         503 $self->server_response($page);
276 5         264 $self->avs_code($col[5]);
277 5         274 $self->order_number($col[6]);
278 5         268 $self->md5($col[37]);
279 5         282 $self->cvv2_response($col[38]);
280 5         300 $self->cavv_response($col[39]);
281              
282 5 100       106 if($col[0] eq "1" ) { # Authorized/Pending/Test
283 4         206 $self->is_success(1);
284 4         233 $self->result_code($col[0]);
285 4 50       83 if ($col[4] =~ /^(.*)\s+(\d+)$/) { #eProcessingNetwork extra bits..
286 0         0 $self->authorization($2);
287             } else {
288 4         193 $self->authorization($col[4]);
289             }
290             } else {
291 1         51 $self->is_success(0);
292 1         55 $self->result_code($col[2]);
293 1         53 $self->error_message($col[3]);
294 1 50       46 if ( $self->result_code ) {
295 1         48 my $addl = $ERRORS{ $self->result_code };
296             $self->error_message( $self->error_message. ' - '. $addl->{notes})
297 1 50 33     83 if $addl && ref($addl) eq 'HASH' && $addl->{notes};
      33        
298             } else { #additional logging information
299             #$page =~ s/\x00/\^0/g;
300             $self->error_message($col[3].
301             " DEBUG: No x_response_code from server, ".
302             "(HTTPS response: $server_response) ".
303             "(HTTPS headers: ".
304 0           join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
  0            
305             "(Raw HTTPS content: $page)"
306             );
307             }
308             }
309             }
310              
311             1;
312             __END__