1 package Business::OnlinePayment::ElavonVirtualMerchant;
2 use base qw(Business::OnlinePayment::HTTPS);
5 use vars qw( $VERSION $DEBUG %maxlength );
13 'info_compat' => '0.01',
14 'gateway_name' => 'ElavonVirtualMerchant',
15 'gateway_url' => 'http://www.myvirtualmerchant.com/',
16 'module_version' => $VERSION,
17 'supported_types' => [ qw( CC ECHECK ) ],
19 'test_transaction' => 1,
20 'supported_actions' => [
21 'Normal Authorization',
22 #'Authorization Only',
23 #'Post Authorization',
33 Business::OnlinePayment::ElavonVirtualMerchant - Elavon Virtual Merchant backend for Business::OnlinePayment
37 use Business::OnlinePayment::ElavonVirtualMerchant;
39 my $tx = new Business::OnlinePayment("ElavonVirtualMerchant", { default_ssl_user_id => 'whatever' });
43 password => '', #password or transaction key
44 action => 'Normal Authorization',
45 description => 'Business::OnlinePayment test',
47 invoice_number => '100100',
49 first_name => 'Jason',
50 last_name => 'Kohles',
51 address => '123 Anystreet',
55 card_number => '4007000000027',
56 expiration => '09/02',
61 if($tx->is_success()) {
62 print "Card processed successfully: ".$tx->authorization."\n";
64 print "Card was rejected: ".$tx->error_message."\n";
69 This module lets you use the Elavon (formerly Nova Information Systems) Converge
70 (formerly Virtual Merchant, a successor of viaKlix) real-time payment gateway
71 from an application that uses the Business::OnlinePayment interface.
73 You need an account with Elavon. Elavon uses a three-part set of credentials to
74 allow you to configure multiple 'virtual terminals'. Since Business::OnlinePayment
75 only passes a login and password with each transaction, you must pass the third item,
76 default_ssl_user_id, to the constructor. You may pass defaults for other Converge
77 request fields to the constructor by prepending the field names with default_.
79 Converge offers a number of transaction types. Of these, only credit card sale
80 (ccsale), credit card refund (cccredit) and echeck sale (ecspurchase) transactions
81 are currently supported.
97 my $level = shift || 0;
99 $self->{"__DEBUG"} = $level;
104 $Business::OnlinePayment::HTTPS::DEBUG = $level;
106 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
111 Sets defaults for the Converge gateway URL
112 and initializes internal data structures.
120 # standard B::OP methods/data
121 $self->server("www.myvirtualmerchant.com");
123 $self->path("/VirtualMerchant/process.do");
125 $self->build_subs(qw(
126 order_number avs_code cvv2_response
127 response_page response_code response_headers
130 # module specific data
131 if ( $opts{debug} ) {
132 $self->debug( $opts{debug} );
137 foreach my $key (keys %opts) {
138 $key =~ /^default_(\w*)$/ or next;
139 $_defaults{$1} = $opts{$key};
142 $self->{_defaults} = \%_defaults;
148 Converts credit card types and transaction types from the Business::OnlinePayment values to Elavon's.
155 my %content = $self->content();
157 if (uc($self->transaction_type) eq 'ECHECK') {
159 $content{'ssl_transaction_type'} = 'ECSPURCHASE';
161 } else { # or credit card, or non-supported type (support checked during submit)
165 'normal authorization' => 'CCSALE', # Authorization/Settle transaction
166 'credit' => 'CCCREDIT', # Credit (refund)
169 $content{'ssl_transaction_type'} = $actions{ lc( $content{'action'} ) }
170 || $content{'action'};
175 'mastercard' => 'CC',
176 'american express' => 'CC',
181 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
183 $self->transaction_type( $content{'type'} );
187 # stuff it back into %content
188 $self->content(%content);
191 =head2 _revmap_fields
193 Accepts I<%map> and sets the content field specified
194 by map keys to be the value of the content field
195 specified by map values, e.g.
197 ssl_merchant_id => 'login'
199 will set ssl_merchant_id to the current value of login.
201 Values may also be references to strings, e.g.
203 ssl_exp_date => \$expdate_mmyy,
205 will set ssl_exp_date to the value of $expdate_mmyy.
210 my ( $self, %map ) = @_;
211 my %content = $self->content();
212 foreach ( keys %map ) {
216 : $content{ $map{$_} };
218 $self->content(%content);
223 Accepts I<$expiration>. Returns mmyy normalized value,
224 or original value if it couldn't be normalized.
230 my $expiration = shift;
232 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
233 my ( $month, $year ) = ( $1, $2 );
234 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
236 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
239 =head2 required_fields
241 Accepts I<@fields> and makes sure each of those fields
242 have been set in content.
246 sub required_fields {
247 my($self,@fields) = @_;
250 my %content = $self->content();
253 if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
257 Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
264 Maps data from Business::OnlinePayment name space to Elavon's, checks that all required fields
265 for the transaction type are present, and submits the transaction. Saves the results.
270 ssl_description => 255,
271 ssl_invoice_number => 25,
272 ssl_customer_code => 17,
274 ssl_first_name => 20,
277 ssl_avs_address => 30,
281 ssl_ship_to_first_name => 20,
282 ssl_ship_to_last_name => 30,
283 ssl_ship_to_company => 50,
284 ssl_ship_to_address1 => 30,
285 ssl_ship_to_city => 30,
286 ssl_ship_to_phone => 20, #though we don't map anything to this...
292 if ($self->test_transaction) {
293 $self->server("demo.myvirtualmerchant.com");
294 $self->path("/VirtualMerchantDemo/process.do");
297 $self->_map_fields();
299 my %content = $self->content;
300 warn "INITIAL PARAMETERS:\n" . join("\n", map{ "$_ => $content{$_}" } keys(%content)) if $self->debug;
303 my @alwaysrequired = qw(
309 $required{CC_CCSALE} = [ @alwaysrequired, qw(
312 ssl_cvv2cvc2_indicator
315 $required{CC_CCCREDIT} = $required{CC_CCSALE};
316 $required{ECHECK_ECSPURCHASE} = [ @alwaysrequired,
319 ssl_bank_account_number
320 ssl_bank_account_type
325 # these are actually each sometimes required, depending on account type & settings,
326 # but we can let converge handle error messages for that
327 # Regarding ssl_user_id...all Elavon docs say this is required,
328 # but apparently CardFortress previously worked without it
329 my @alwaysoptional = qw(
336 $optional{CC_CCSALE} = [ @alwaysoptional, qw( ssl_salestax ssl_cvv2cvc2
337 ssl_description ssl_invoice_number
339 ssl_avs_address ssl_address2
340 ssl_city ssl_state ssl_avs_zip ssl_country
341 ssl_phone ssl_ship_to_company
342 ssl_ship_to_first_name ssl_ship_to_last_name
343 ssl_ship_to_address1 ssl_ship_to_city
344 ssl_ship_to_state ssl_ship_to_zip
347 $optional{CC_CCCREDIT} = $optional{CC_CCSALE};
348 $optional{ECHECK_ECSPURCHASE} = [ @alwaysoptional ];
350 my $type_action = $self->transaction_type(). '_'. $content{ssl_transaction_type};
351 unless ( exists($required{$type_action}) ) {
352 $self->error_message("Elavon can't handle transaction type: ".
353 "$content{action} on " . $self->transaction_type() );
354 $self->is_success(0);
358 $self->_revmap_fields(
359 ssl_merchant_id => 'login',
360 ssl_pin => 'password',
361 ssl_amount => 'amount',
362 ssl_first_name => 'first_name',
363 ssl_last_name => 'last_name',
364 ssl_company => 'company',
365 ssl_email => 'email',
368 if (uc($self->transaction_type) eq 'CC') {
370 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
371 my $zip = $content{'zip'};
372 $zip =~ s/[^[:alnum:]]//g;
374 my $cvv2indicator = $content{"cvv2"} ? 1 : 9; # 1 = Present, 9 = Not Present
376 $self->_revmap_fields(
378 ssl_card_number => 'card_number',
379 ssl_exp_date => \$expdate_mmyy, # MMYY from 'expiration'
380 ssl_cvv2cvc2_indicator => \$cvv2indicator,
381 ssl_cvv2cvc2 => 'cvv2',
382 ssl_description => 'description',
383 ssl_invoice_number => 'invoice_number',
384 ssl_customer_code => 'customer_id',
386 ssl_avs_address => 'address',
388 ssl_state => 'state',
389 ssl_avs_zip => \$zip, # 'zip' with non-alnums removed
390 ssl_country => 'country',
391 ssl_phone => 'phone',
393 ssl_ship_to_first_name => 'ship_first_name',
394 ssl_ship_to_last_name => 'ship_last_name',
395 ssl_ship_to_company => 'ship_company',
396 ssl_ship_to_address1 => 'ship_address',
397 ssl_ship_to_city => 'ship_city',
398 ssl_ship_to_state => 'ship_state',
399 ssl_ship_to_zip => 'ship_zip',
400 ssl_ship_to_country => 'ship_country',
407 if (uc($content{'account_type'}) =~ 'PERSONAL') {
409 } elsif (uc($content{'account_type'}) =~ 'BUSINESS') {
412 $self->error_message("Unrecognized account type: ".$content{'account_type'});
413 $self->is_success(0);
417 $self->_revmap_fields(
418 ssl_aba_number => 'routing_code',
419 ssl_bank_account_number => 'account_number',
420 ssl_bank_account_type => \$account_type,
426 # set defaults for anything that hasn't been set yet
427 %content = $self->content;
428 foreach ( keys ( %{($self->{_defaults})} ) ) {
429 $content{$_} ||= $self->{_defaults}->{$_};
431 $self->content(%content);
433 # truncate long rows & validate required fields
434 my %params = $self->get_fields( @{$required{$type_action}},
435 @{$optional{$type_action}},
437 $params{$_} = substr($params{$_},0,$maxlength{$_})
438 foreach grep exists($maxlength{$_}), keys %params;
439 $self->required_fields(@{$required{$type_action}});
441 # some final non-overridable parameters
442 $params{ssl_test_mode}='true' if $self->test_transaction;
443 $params{ssl_show_form}='false';
444 $params{ssl_result_format}='ASCII';
447 warn "CONNECTING TO " . $self->server . ':' . $self->port . $self->path if $self->debug;
448 warn "POST PARAMETERS:\n" . join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $self->debug;
449 my ( $page, $resp, %resp_headers ) =
450 $self->https_post( %params );
452 $self->response_code( $resp );
453 $self->response_page( $page );
454 $self->response_headers( \%resp_headers );
456 warn "RESPONSE FROM SERVER:\n$page\n" if $self->debug;
457 # $page should contain key/value pairs
460 my %results = map { s/\s*$//; split '=', $_, 2 } grep { /=/ } split '^', $page;
462 if (uc($self->transaction_type) eq 'CC') {
463 # AVS and CVS values may be set on success or failure
464 $self->avs_code( $results{ssl_avs_response} );
465 $self->cvv2_response( $results{ ssl_cvv2_response } );
467 $self->result_code( $status = $results{ errorCode } || $results{ ssl_result } );
468 $self->order_number( $results{ ssl_txn_id } );
469 $self->authorization( $results{ ssl_approval_code } );
470 $self->error_message( $results{ errorMessage } || $results{ ssl_result_message } );
473 if ( $resp =~ /^(HTTP\S+ )?200/ && $status eq "0" ) {
474 $self->is_success(1);
476 $self->is_success(0);
485 L<Business::OnlinePayment>, L<Business::OnlinePayment::HTTPS>, Elavon Converge Developers' Guide
489 Duplicates code to handle deprecated 'type' codes.
491 Only provides a small selection of possible transaction types.
493 =head1 COPYRIGHT AND LICENSE
495 Copyright (C) 2016 Freeside Internet Services.
497 Based on the original ElavonVirtualMerchant module by Richard Siddall,
498 which was largely based on Business::OnlinePayment::viaKlix by Jeff Finucane.
500 This library is free software; you can redistribute it and/or modify
501 it under the same terms as Perl itself, either Perl version 5.8.8 or,
502 at your option, any later version of Perl 5 you may have available.