File Coverage

blib/lib/Business/OnlinePayment/ElavonVirtualMerchant.pm
Criterion Covered Total %
statement 15 75 20.0
branch 0 14 0.0
condition 0 15 0.0
subroutine 4 6 66.6
pod 2 2 100.0
total 21 112 18.7


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::ElavonVirtualMerchant;
2 2     2   19585 use base qw(Business::OnlinePayment::viaKLIX);
  2         2  
  2         924  
3              
4 2     2   38006 use strict;
  2         3  
  2         40  
5 2     2   5 use vars qw( $VERSION %maxlength );
  2         6  
  2         1329  
6              
7             $VERSION = '0.03';
8             $VERSION = eval $VERSION;
9              
10             =head1 NAME
11              
12             Business::OnlinePayment::ElavonVirtualMerchant - Elavon Virtual Merchant backend for Business::OnlinePayment
13              
14             =head1 SYNOPSIS
15              
16             use Business::OnlinePayment::ElavonVirtualMerchant;
17              
18             my $tx = new Business::OnlinePayment("ElavonVirtualMerchant", { default_ssl_userid => 'whatever' });
19             $tx->content(
20             type => 'VISA',
21             login => 'testdrive',
22             password => '', #password or transaction key
23             action => 'Normal Authorization',
24             description => 'Business::OnlinePayment test',
25             amount => '49.95',
26             invoice_number => '100100',
27             customer_id => 'jsk',
28             first_name => 'Jason',
29             last_name => 'Kohles',
30             address => '123 Anystreet',
31             city => 'Anywhere',
32             state => 'UT',
33             zip => '84058',
34             card_number => '4007000000027',
35             expiration => '09/02',
36             cvv2 => '1234', #optional
37             );
38             $tx->submit();
39              
40             if($tx->is_success()) {
41             print "Card processed successfully: ".$tx->authorization."\n";
42             } else {
43             print "Card was rejected: ".$tx->error_message."\n";
44             }
45              
46             =head1 DESCRIPTION
47              
48             This module lets you use the Elavon (formerly Nova Information Systems) Virtual Merchant real-time payment gateway, a successor to viaKlix, from an application that uses the Business::OnlinePayment interface.
49              
50             You need an account with Elavon. Elavon uses a three-part set of credentials to allow you to configure multiple 'virtual terminals'. Since Business::OnlinePayment only passes a login and password with each transaction, you must pass the third item, the user_id, to the constructor.
51              
52             Elavon offers a number of transaction types, including electronic gift card operations and 'PINless debit'. Of these, only credit card transactions fit the Business::OnlinePayment model.
53              
54             Since the Virtual Merchant API is just a newer version of the viaKlix API, this module subclasses Business::OnlinePayment::viaKlix.
55              
56             This module does not use Elavon's XML encoding as this doesn't appear to offer any benefit over the standard encoding.
57              
58             =head1 SUBROUTINES
59              
60             =head2 set_defaults
61              
62             Sets defaults for the Virtual Merchant gateway URL.
63              
64             =cut
65              
66             sub set_defaults {
67 2     2 1 2878 my $self = shift;
68 2         3 my %opts = @_;
69              
70 2         9 $self->SUPER::set_defaults(%opts);
71             # standard B::OP methods/data
72 2         270 $self->server("www.myvirtualmerchant.com");
73 2         37 $self->port("443");
74 2         57 $self->path("/VirtualMerchant/process.do");
75              
76             }
77              
78             =head2 _map_fields
79              
80             Converts credit card types and transaction types from the Business::OnlinePayment values to Elavon's.
81              
82             =cut
83              
84             sub _map_fields {
85 0     0     my ($self) = @_;
86              
87 0           my %content = $self->content();
88              
89             #ACTION MAP
90 0           my %actions = (
91             'normal authorization' => 'CCSALE', # Authorization/Settle transaction
92             'credit' => 'CCCREDIT', # Credit (refund)
93             );
94              
95             $content{'ssl_transaction_type'} = $actions{ lc( $content{'action'} ) }
96 0   0       || $content{'action'};
97              
98             # TYPE MAP
99 0           my %types = (
100             'visa' => 'CC',
101             'mastercard' => 'CC',
102             'american express' => 'CC',
103             'discover' => 'CC',
104             'cc' => 'CC',
105             );
106              
107 0   0       $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
108              
109 0           $self->transaction_type( $content{'type'} );
110              
111             # stuff it back into %content
112 0           $self->content(%content);
113             }
114              
115             =head2 submit
116              
117             Maps data from Business::OnlinePayment name space to Elavon's, checks that all required fields
118             for the transaction type are present, and submits the transaction. Saves the results.
119              
120             =cut
121              
122             %maxlength = (
123             ssl_description => 255,
124             ssl_invoice_number => 25,
125             ssl_customer_code => 17,
126              
127             ssl_first_name => 20,
128             ssl_last_name => 30,
129             ssl_company => 50,
130             ssl_avs_address => 30,
131             ssl_city => 30,
132             ssl_phone => 20,
133              
134             ssl_ship_to_first_name => 20,
135             ssl_ship_to_last_name => 30,
136             ssl_ship_to_company => 50,
137             ssl_ship_to_address1 => 30,
138             ssl_ship_to_city => 30,
139             ssl_ship_to_phone => 20, #though we don't map anything to this...
140             );
141              
142             sub submit {
143 0     0 1   my ($self) = @_;
144              
145 0           $self->_map_fields();
146              
147 0           my %content = $self->content;
148              
149 0           my %required;
150 0           $required{CC_CCSALE} = [ qw( ssl_transaction_type ssl_merchant_id ssl_pin
151             ssl_amount ssl_card_number ssl_exp_date
152             ssl_cvv2cvc2_indicator
153             ) ];
154 0           $required{CC_CCCREDIT} = $required{CC_CCSALE};
155 0           my %optional;
156 0           $optional{CC_CCSALE} = [ qw( ssl_user_id ssl_salestax ssl_cvv2cvc2
157             ssl_description ssl_invoice_number
158             ssl_customer_code ssl_company ssl_first_name
159             ssl_last_name ssl_avs_address ssl_address2
160             ssl_city ssl_state ssl_avs_zip ssl_country
161             ssl_phone ssl_email ssl_ship_to_company
162             ssl_ship_to_first_name ssl_ship_to_last_name
163             ssl_ship_to_address1 ssl_ship_to_city
164             ssl_ship_to_state ssl_ship_to_zip
165             ssl_ship_to_country
166             ) ];
167 0           $optional{CC_CCCREDIT} = $optional{CC_CCSALE};
168              
169 0           my $type_action = $self->transaction_type(). '_'. $content{ssl_transaction_type};
170 0 0         unless ( exists($required{$type_action}) ) {
171 0           $self->error_message("Elavon can't handle transaction type: ".
172             "$content{action} on " . $self->transaction_type() );
173 0           $self->is_success(0);
174 0           return;
175             }
176              
177 0           my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
178 0           my $zip = $content{'zip'};
179 0           $zip =~ s/[^[:alnum:]]//g;
180              
181 0 0         my $cvv2indicator = $content{"cvv2"} ? 1 : 9; # 1 = Present, 9 = Not Present
182              
183 0           $self->_revmap_fields(
184              
185             ssl_merchant_id => 'login',
186             ssl_pin => 'password',
187              
188             ssl_amount => 'amount',
189             ssl_card_number => 'card_number',
190             ssl_exp_date => \$expdate_mmyy, # MMYY from 'expiration'
191             ssl_cvv2cvc2_indicator => \$cvv2indicator,
192             ssl_cvv2cvc2 => 'cvv2',
193             ssl_description => 'description',
194             ssl_invoice_number => 'invoice_number',
195             ssl_customer_code => 'customer_id',
196              
197             ssl_first_name => 'first_name',
198             ssl_last_name => 'last_name',
199             ssl_company => 'company',
200             ssl_avs_address => 'address',
201             ssl_city => 'city',
202             ssl_state => 'state',
203             ssl_avs_zip => \$zip, # 'zip' with non-alnums removed
204             ssl_country => 'country',
205             ssl_phone => 'phone',
206             ssl_email => 'email',
207              
208             ssl_ship_to_first_name => 'ship_first_name',
209             ssl_ship_to_last_name => 'ship_last_name',
210             ssl_ship_to_company => 'ship_company',
211             ssl_ship_to_address1 => 'ship_address',
212             ssl_ship_to_city => 'ship_city',
213             ssl_ship_to_state => 'ship_state',
214             ssl_ship_to_zip => 'ship_zip',
215             ssl_ship_to_country => 'ship_country',
216              
217             );
218              
219 0           my %params = $self->get_fields( @{$required{$type_action}},
220 0           @{$optional{$type_action}},
  0            
221             );
222              
223             $params{$_} = substr($params{$_},0,$maxlength{$_})
224 0           foreach grep exists($maxlength{$_}), keys %params;
225              
226 0           foreach ( keys ( %{($self->{_defaults})} ) ) {
  0            
227 0 0         $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_});
228             }
229              
230 0 0         $params{ssl_test_mode}='true' if $self->test_transaction;
231            
232 0           $params{ssl_show_form}='false';
233 0           $params{ssl_result_format}='ASCII';
234              
235 0           $self->required_fields(@{$required{$type_action}});
  0            
236            
237 0 0         warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $self->debug > 1;
  0            
238 0           my ( $page, $resp, %resp_headers ) =
239             $self->https_post( %params );
240              
241 0           $self->response_code( $resp );
242 0           $self->response_page( $page );
243 0           $self->response_headers( \%resp_headers );
244              
245 0 0         warn "$page\n" if $self->debug > 1;
246             # $page should contain key/value pairs
247              
248 0           my $status ='';
249 0           my %results = map { s/\s*$//; split '=', $_, 2 } grep { /=/ } split '^', $page;
  0            
  0            
  0            
250              
251             # AVS and CVS values may be set on success or failure
252 0           $self->avs_code( $results{ssl_avs_response} );
253 0           $self->cvv2_response( $results{ ssl_cvv2_response } );
254 0   0       $self->result_code( $status = $results{ errorCode } || $results{ ssl_result } );
255 0           $self->order_number( $results{ ssl_txn_id } );
256 0           $self->authorization( $results{ ssl_approval_code } );
257 0   0       $self->error_message( $results{ errorMessage } || $results{ ssl_result_message } );
258              
259              
260 0 0 0       if ( $resp =~ /^(HTTP\S+ )?200/ && $status eq "0" ) {
261 0           $self->is_success(1);
262             } else {
263 0           $self->is_success(0);
264             }
265             }
266              
267             1;
268             __END__