File Coverage

blib/lib/Business/OnlinePayment/ElavonVirtualMerchant.pm
Criterion Covered Total %
statement 18 77 23.3
branch 0 14 0.0
condition 0 15 0.0
subroutine 5 7 71.4
pod 2 2 100.0
total 25 115 21.7


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