File Coverage

blib/lib/Business/OnlinePayment/NMI.pm
Criterion Covered Total %
statement 37 93 39.7
branch 6 36 16.6
condition 1 2 50.0
subroutine 9 11 81.8
pod 1 3 33.3
total 54 145 37.2


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::NMI;
2              
3 3     3   26292 use strict;
  3         7  
  3         114  
4 3     3   17 use Carp;
  3         7  
  3         240  
5 3     3   874 use Business::OnlinePayment 3;
  3         4160  
  3         85  
6 3     3   2915 use Business::OnlinePayment::HTTPS;
  3         122681  
  3         121  
7 3     3   35 use Digest::MD5 qw(md5_hex);
  3         7  
  3         274  
8 3     3   17 use URI::Escape;
  3         5  
  3         192  
9 3     3   19 use vars qw($VERSION @ISA $DEBUG);
  3         5  
  3         12597  
10              
11             @ISA = qw(Business::OnlinePayment::HTTPS);
12             $VERSION = '0.03';
13              
14             $DEBUG = 0;
15              
16             sub _info {
17             {
18 0     0   0 'info_compat' => '0.01',
19             'gateway_name' => 'Network Merchants',
20             'gateway_url' => 'https://www.nmi.com',
21             'module_version' => $VERSION,
22             'supported_types' => [ 'CC', 'ECHECK' ],
23             'supported_actions' => {
24             CC => [
25             'Normal Authorization',
26             'Authorization Only',
27             'Post Authorization',
28             'Credit',
29             'Void',
30             ],
31             ECHECK => [
32             'Normal Authorization',
33             'Credit',
34             'Void',
35             ],
36             },
37             };
38             }
39              
40             my %actions = (
41             'normal authorization' => 'sale',
42             'authorization only' => 'auth',
43             'post authorization' => 'capture',
44             'credit' => 'refund',
45             'void' => 'void',
46             );
47             my %types = (
48             'cc' => 'creditcard',
49             'echeck' => 'check',
50             );
51              
52             my %fields = (
53             # NMI Direct Post API, June 2007
54             action => 'type', # special
55             login => 'username',
56             password => 'password',
57             card_number => 'ccnumber',
58             expiration => 'ccexp',
59             name => 'checkname',
60             routing_code => 'checkaba',
61             account_number => 'checkaccount',
62             account_holder_type => 'account_holder_type',
63             account_type => 'account_type',
64             amount => 'amount',
65             cvv2 => 'cvv',
66             payment => 'payment', # special
67             description => 'orderdescription',
68             invoice_number => 'orderid',
69             customer_ip => 'ipaddress',
70             tax => 'tax',
71             freight => 'shipping',
72             po_number => 'ponumber',
73             first_name => 'firstname',
74             last_name => 'lastname',
75             company => 'company',
76             address => 'address1',
77             city => 'city',
78             state => 'state',
79             zip => 'zip',
80             country => 'country',
81             order_number => 'transactionid', # used for capture/void/refund
82             );
83              
84             $fields{"ship_$_"} = 'shipping_'.$fields{$_}
85             foreach(qw(first_name last_name company address city state zip country)) ;
86              
87             my %required = (
88             'ALL' => [ qw( type username password payment ) ],
89             'sale' => [ 'amount' ],
90             'sale:creditcard' => [ 'ccnumber', 'ccexp' ],
91             'sale:check' => [ qw( checkname checkaba checkaccount account_holder_type account_type ) ],
92             'auth:creditcard' => [ qw( amount ccnumber ccexp ) ],
93             'capture' => [ 'amount', 'transactionid' ],
94             'refund' => [ 'amount', 'transactionid' ],
95             'void' => [ 'transactionid' ],
96             # not supported: update
97             ),
98              
99             my %optional = (
100             'ALL' => [],
101             'sale' => [ qw( orderdescription orderid ipaddress tax
102             shipping ponumber firstname lastname company
103             address1 city state zip country phone fax email
104             shipping_firstname shipping_lastname
105             shipping_company shipping_address1 shipping_city
106             shipping_state shipping_zip shipping_country
107             ) ],
108             'sale:creditcard' => [ 'cvv' ],
109             'sale:check' => [],
110             'auth:creditcard' => [ qw( orderdescription orderid ipaddress tax
111             shipping ponumber firstname lastname company
112             address1 city state zip country phone fax email
113             shipping_firstname shipping_lastname
114             shipping_company shipping_address1 shipping_city
115             shipping_state shipping_zip shipping_country
116             cvv ) ],
117             'capture' => [ 'orderid' ],
118             'refund' => [ 'amount' ],
119             );
120              
121             my %failure_status = (
122             200 => 'decline',
123             201 => 'decline',
124             202 => 'nsf',
125             203 => 'nsf',
126             223 => 'expired',
127             250 => 'pickup',
128             252 => 'stolen',
129             # add others here as needed; very little code uses failure_status at present
130             );
131              
132             sub set_defaults {
133 9     9 0 9132 my $self = shift;
134 9         295 $self->server('secure.networkmerchants.com');
135 9         351 $self->port('443');
136 9         397 $self->path('/api/transact.php');
137 9         131 $self->build_subs(qw(avs_code cvv2_response failure_status));
138             }
139              
140             sub map_fields {
141 9     9 0 27 my($self) = shift;
142              
143 9         32 my %content = $self->content();
144              
145 9 50       1117 if($self->test_transaction) {
146             # Public test account.
147 0         0 $content{'login'} = 'demo';
148 0         0 $content{'password'} = 'password';
149             }
150              
151 9 50       258 $content{'payment'} = $types{lc($content{'type'})} or die "Payment method '$content{type}' not supported.\n";
152 9 50       50 $content{'action'} = $actions{lc($content{'action'})} or die "Transaction type '$content{action}' not supported.\n";
153              
154 9 100       47 $content{'expiration'} =~ s/\D//g if defined($content{'expiration'});
155              
156 9   50     69 $content{'account_type'} ||= 'personal checking';
157 18         69 @content{'account_holder_type', 'account_type'} =
158 9         57 map {lc} split /\s/, $content{'account_type'};
159 9 50       43 $content{'ship_name'} = $content{'ship_first_name'} ?
160             ($content{'ship_first_name'}.' '.$content{'ship_last_name'}) : '';
161 9         56 $self->content(%content);
162             }
163              
164             sub submit {
165 0     0 1   my($self) = @_;
166              
167 0           $self->map_fields();
168              
169 0           $self->remap_fields(%fields);
170              
171 0           my %content = $self->content;
172 0           my $type = $content{'type'}; # what we call "action"
173 0           my $payment = $content{'payment'}; # what we call "type"
174 0 0         if ( $DEBUG >= 3 ) {
175 0           warn "content:$_ => $content{$_}\n" foreach keys %content;
176             }
177              
178 0           my @required_fields = ( @{$required{'ALL'}} );
  0            
179 0 0         push @required_fields, @{$required{$type}} if exists($required{$type});
  0            
180 0 0         push @required_fields, @{$required{"$type:$payment"}} if exists($required{"$type:$payment"});
  0            
181              
182 0           $self->required_fields(@required_fields);
183              
184 0           my @allowed_fields = @required_fields;
185 0           push @allowed_fields, @{$optional{'ALL'}};
  0            
186 0 0         push @allowed_fields, @{$optional{$type}} if exists($optional{$type});
  0            
187 0 0         push @allowed_fields, @{$optional{"$type:$payment"}} if exists($required{"$type:$payment"});
  0            
188              
189 0           my %post_data = $self->get_fields(@allowed_fields);
190              
191 0 0         if ( $DEBUG ) {
192 0           warn "post_data:$_ => $post_data{$_}\n" foreach keys %post_data;
193             }
194              
195 0           my($page,$server_response) = $self->https_post(\%post_data);
196 0 0         if ( $DEBUG ) {
197 0           warn "response page: $page\n";
198             }
199              
200 0           my $response;
201 0 0         if ($server_response =~ /200/){
202 0           $response = {map { split '=', $_, 2 } split '&', $page};
  0            
203             }
204             else {
205 0           die "HTTPS error: '$server_response'\n";
206             }
207              
208 0           $response->{$_} =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg
209 0           foreach keys %$response;
210              
211 0 0         if ( $DEBUG ) {
212 0           warn "response:$_ => $response->{$_}\n" foreach keys %$response;
213             }
214              
215 0           $self->is_success(0);
216 0           my $error;
217 0 0         if( $response->{response} == 1 ) {
    0          
    0          
218 0           $self->is_success(1);
219             }
220             elsif( $response->{response} == 2 ) {
221 0           $error = $response->{responsetext};
222 0           my $code = $response->{response_code};
223 0 0         $self->failure_status($failure_status{$code}) if exists($failure_status{$code});
224             }
225             elsif( $response->{response} == 3 ) {
226 0           $error = "Transaction error: '".$response->{responsetext}."'";
227             }
228             else {
229 0           $error = "Could not interpret server response: '$page'";
230             }
231 0           $self->order_number($response->{transactionid});
232 0           $self->authorization($response->{authcode});
233 0           $self->avs_code($response->{avsresponse});
234 0           $self->cvv2_response($response->{cvvresponse});
235 0           $self->result_code($response->{response_code});
236 0           $self->error_message($error);
237 0           $self->server_response($response);
238             }
239              
240             1;
241             __END__