1 package Business::OnlinePayment::ElavonVirtualMerchant;
2 use base qw(Business::OnlinePayment::HTTPS);
5 use vars qw( $VERSION $DEBUG %maxlength );
9 $VERSION = eval $VERSION;
14 Business::OnlinePayment::ElavonVirtualMerchant - Elavon Virtual Merchant backend for Business::OnlinePayment
18 use Business::OnlinePayment::ElavonVirtualMerchant;
20 my $tx = new Business::OnlinePayment("ElavonVirtualMerchant", { default_ssl_user_id => 'whatever' });
24 password => '', #password or transaction key
25 action => 'Normal Authorization',
26 description => 'Business::OnlinePayment test',
28 invoice_number => '100100',
30 first_name => 'Jason',
31 last_name => 'Kohles',
32 address => '123 Anystreet',
36 card_number => '4007000000027',
37 expiration => '09/02',
42 if($tx->is_success()) {
43 print "Card processed successfully: ".$tx->authorization."\n";
45 print "Card was rejected: ".$tx->error_message."\n";
50 This module lets you use the Elavon (formerly Nova Information Systems) Converge
51 (formerly Virtual Merchant, a successor of viaKlix) real-time payment gateway
52 from an application that uses the Business::OnlinePayment interface.
54 You need an account with Elavon. Elavon uses a three-part set of credentials to
55 allow you to configure multiple 'virtual terminals'. Since Business::OnlinePayment
56 only passes a login and password with each transaction, you must pass the third item,
57 default_ssl_user_id, to the constructor. You may pass defaults for other Converge
58 request fields to the constructor by prepending the field names with default_.
60 Converge offers a number of transaction types. Of these, only credit card sale
61 (ccsale), credit card refund (cccredit) and echeck sale (ecspurchase) transactions
62 are currently supported.
78 my $level = shift || 0;
80 $self->{"__DEBUG"} = $level;
85 $Business::OnlinePayment::HTTPS::DEBUG = $level;
87 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
92 Sets defaults for the Converge gateway URL
93 and initializes internal data structures.
101 # standard B::OP methods/data
102 $self->server("www.myvirtualmerchant.com");
104 $self->path("/VirtualMerchant/process.do");
106 $self->build_subs(qw(
107 order_number avs_code cvv2_response
108 response_page response_code response_headers
111 # module specific data
112 if ( $opts{debug} ) {
113 $self->debug( $opts{debug} );
118 foreach my $key (keys %opts) {
119 $key =~ /^default_(\w*)$/ or next;
120 $_defaults{$1} = $opts{$key};
123 $self->{_defaults} = \%_defaults;
129 Converts credit card types and transaction types from the Business::OnlinePayment values to Elavon's.
136 my %content = $self->content();
138 if (uc($self->transaction_type) eq 'ECHECK') {
140 $content{'ssl_transaction_type'} = 'ECSPURCHASE';
142 } else { # or credit card, or non-supported type (support checked during submit)
146 'normal authorization' => 'CCSALE', # Authorization/Settle transaction
147 'credit' => 'CCCREDIT', # Credit (refund)
150 $content{'ssl_transaction_type'} = $actions{ lc( $content{'action'} ) }
151 || $content{'action'};
156 'mastercard' => 'CC',
157 'american express' => 'CC',
162 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
164 $self->transaction_type( $content{'type'} );
168 # stuff it back into %content
169 $self->content(%content);
172 =head2 _revmap_fields
174 Accepts I<%map> and sets the content field specified
175 by map keys to be the value of the content field
176 specified by map values, e.g.
178 ssl_merchant_id => 'login'
180 will set ssl_merchant_id to the current value of login.
182 Values may also be references to strings, e.g.
184 ssl_exp_date => \$expdate_mmyy,
186 will set ssl_exp_date to the value of $expdate_mmyy.
191 my ( $self, %map ) = @_;
192 my %content = $self->content();
193 foreach ( keys %map ) {
197 : $content{ $map{$_} };
199 $self->content(%content);
204 Accepts I<$expiration>. Returns mmyy normalized value,
205 or original value if it couldn't be normalized.
211 my $expiration = shift;
213 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
214 my ( $month, $year ) = ( $1, $2 );
215 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
217 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
220 =head2 required_fields
222 Accepts I<@fields> and makes sure each of those fields
223 have been set in content.
227 sub required_fields {
228 my($self,@fields) = @_;
231 my %content = $self->content();
234 if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
238 Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
245 Maps data from Business::OnlinePayment name space to Elavon's, checks that all required fields
246 for the transaction type are present, and submits the transaction. Saves the results.
251 ssl_description => 255,
252 ssl_invoice_number => 25,
253 ssl_customer_code => 17,
255 ssl_first_name => 20,
258 ssl_avs_address => 30,
262 ssl_ship_to_first_name => 20,
263 ssl_ship_to_last_name => 30,
264 ssl_ship_to_company => 50,
265 ssl_ship_to_address1 => 30,
266 ssl_ship_to_city => 30,
267 ssl_ship_to_phone => 20, #though we don't map anything to this...
273 $self->_map_fields();
275 my %content = $self->content;
276 warn "INITIAL PARAMETERS:\n" . join("\n", map{ "$_ => $content{$_}" } keys(%content)) if $self->debug;
279 my @alwaysrequired = qw(
286 $required{CC_CCSALE} = [ @alwaysrequired, qw(
289 ssl_cvv2cvc2_indicator
292 $required{CC_CCCREDIT} = $required{CC_CCSALE};
293 $required{ECHECK_ECSPURCHASE} = [ @alwaysrequired,
296 ssl_bank_account_number
297 ssl_bank_account_type
302 # these are actually each sometimes required, depending on account type & settings,
303 # but we can let converge handle error messages for that
304 my @alwaysoptional = qw(
310 $optional{CC_CCSALE} = [ @alwaysoptional, qw( ssl_salestax ssl_cvv2cvc2
311 ssl_description ssl_invoice_number
313 ssl_avs_address ssl_address2
314 ssl_city ssl_state ssl_avs_zip ssl_country
315 ssl_phone ssl_ship_to_company
316 ssl_ship_to_first_name ssl_ship_to_last_name
317 ssl_ship_to_address1 ssl_ship_to_city
318 ssl_ship_to_state ssl_ship_to_zip
321 $optional{CC_CCCREDIT} = $optional{CC_CCSALE};
322 $optional{ECHECK_ECSPURCHASE} = [ @alwaysoptional ];
324 my $type_action = $self->transaction_type(). '_'. $content{ssl_transaction_type};
325 unless ( exists($required{$type_action}) ) {
326 $self->error_message("Elavon can't handle transaction type: ".
327 "$content{action} on " . $self->transaction_type() );
328 $self->is_success(0);
332 $self->_revmap_fields(
333 ssl_merchant_id => 'login',
334 ssl_pin => 'password',
335 ssl_amount => 'amount',
336 ssl_first_name => 'first_name',
337 ssl_last_name => 'last_name',
338 ssl_company => 'company',
339 ssl_email => 'email',
342 if (uc($self->transaction_type) eq 'CC') {
344 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
345 my $zip = $content{'zip'};
346 $zip =~ s/[^[:alnum:]]//g;
348 my $cvv2indicator = $content{"cvv2"} ? 1 : 9; # 1 = Present, 9 = Not Present
350 $self->_revmap_fields(
352 ssl_card_number => 'card_number',
353 ssl_exp_date => \$expdate_mmyy, # MMYY from 'expiration'
354 ssl_cvv2cvc2_indicator => \$cvv2indicator,
355 ssl_cvv2cvc2 => 'cvv2',
356 ssl_description => 'description',
357 ssl_invoice_number => 'invoice_number',
358 ssl_customer_code => 'customer_id',
360 ssl_avs_address => 'address',
362 ssl_state => 'state',
363 ssl_avs_zip => \$zip, # 'zip' with non-alnums removed
364 ssl_country => 'country',
365 ssl_phone => 'phone',
367 ssl_ship_to_first_name => 'ship_first_name',
368 ssl_ship_to_last_name => 'ship_last_name',
369 ssl_ship_to_company => 'ship_company',
370 ssl_ship_to_address1 => 'ship_address',
371 ssl_ship_to_city => 'ship_city',
372 ssl_ship_to_state => 'ship_state',
373 ssl_ship_to_zip => 'ship_zip',
374 ssl_ship_to_country => 'ship_country',
381 if (uc($content{'account_type'}) =~ 'PERSONAL') {
383 } elsif (uc($content{'account_type'}) =~ 'BUSINESS') {
386 $self->error_message("Unrecognized account type: ".$content{'account_type'});
387 $self->is_success(0);
391 $self->_revmap_fields(
392 ssl_aba_number => 'routing_code',
393 ssl_bank_account_number => 'account_number',
394 ssl_bank_account_type => \$account_type,
400 # set defaults for anything that hasn't been set yet
401 %content = $self->content;
402 foreach ( keys ( %{($self->{_defaults})} ) ) {
403 $content{$_} ||= $self->{_defaults}->{$_};
405 $self->content(%content);
407 # truncate long rows & validate required fields
408 my %params = $self->get_fields( @{$required{$type_action}},
409 @{$optional{$type_action}},
411 $params{$_} = substr($params{$_},0,$maxlength{$_})
412 foreach grep exists($maxlength{$_}), keys %params;
413 $self->required_fields(@{$required{$type_action}});
415 # some final non-overridable parameters
416 $params{ssl_test_mode}='true' if $self->test_transaction;
417 $params{ssl_show_form}='false';
418 $params{ssl_result_format}='ASCII';
421 warn "POST PARAMETERS:\n" . join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $self->debug;
422 my ( $page, $resp, %resp_headers ) =
423 $self->https_post( %params );
425 $self->response_code( $resp );
426 $self->response_page( $page );
427 $self->response_headers( \%resp_headers );
429 warn "RESPONSE FROM SERVER:\n$page\n" if $self->debug;
430 # $page should contain key/value pairs
433 my %results = map { s/\s*$//; split '=', $_, 2 } grep { /=/ } split '^', $page;
435 if (uc($self->transaction_type) eq 'CC') {
436 # AVS and CVS values may be set on success or failure
437 $self->avs_code( $results{ssl_avs_response} );
438 $self->cvv2_response( $results{ ssl_cvv2_response } );
440 $self->result_code( $status = $results{ errorCode } || $results{ ssl_result } );
441 $self->order_number( $results{ ssl_txn_id } );
442 $self->authorization( $results{ ssl_approval_code } );
443 $self->error_message( $results{ errorMessage } || $results{ ssl_result_message } );
446 if ( $resp =~ /^(HTTP\S+ )?200/ && $status eq "0" ) {
447 $self->is_success(1);
449 $self->is_success(0);
458 L<Business::OnlinePayment>, L<Business::OnlinePayment::HTTPS>, Elavon Converge Developers' Guide
462 Duplicates code to handle deprecated 'type' codes.
464 Only provides a small selection of possible transaction types.
466 =head1 COPYRIGHT AND LICENSE
468 Copyright (C) 2016 Freeside Internet Services.
470 Based on the original ElavonVirtualMerchant module by Richard Siddall,
471 which was largely based on Business::OnlinePayment::viaKlix by Jeff Finucane.
473 This library is free software; you can redistribute it and/or modify
474 it under the same terms as Perl itself, either Perl version 5.8.8 or,
475 at your option, any later version of Perl 5 you may have available.