--- /dev/null
+Revision history for Perl extension Business::OnlinePayment::ElavonVirtualMerchant.
+
+0.03 unreleased
+ - Truncate fields to their maximum lengths
+
+0.02 Fri Mar 04 19:00:05 2011
+ - Added ship-to and company fields. Patch from Josh Rosenbaum.
+ - Modified response parser to handle magic numbers surrounding the name/value
+ pairs. Reported by Ivan Kohler.
+
+0.01 Fri Mar 20 20:43:30 2009
+ - original version
+
--- /dev/null
+package Business::OnlinePayment::ElavonVirtualMerchant;
+use base qw(Business::OnlinePayment::viaKLIX);
+
+use strict;
+use vars qw( $VERSION %maxlength );
+
+$VERSION = '0.03';
+$VERSION = eval $VERSION;
+
+=head1 NAME
+
+Business::OnlinePayment::ElavonVirtualMerchant - Elavon Virtual Merchant backend for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+ use Business::OnlinePayment::ElavonVirtualMerchant;
+
+ my $tx = new Business::OnlinePayment("ElavonVirtualMerchant", { default_ssl_userid => 'whatever' });
+ $tx->content(
+ type => 'VISA',
+ login => 'testdrive',
+ password => '', #password or transaction key
+ action => 'Normal Authorization',
+ description => 'Business::OnlinePayment test',
+ amount => '49.95',
+ invoice_number => '100100',
+ customer_id => 'jsk',
+ first_name => 'Jason',
+ last_name => 'Kohles',
+ address => '123 Anystreet',
+ city => 'Anywhere',
+ state => 'UT',
+ zip => '84058',
+ card_number => '4007000000027',
+ expiration => '09/02',
+ cvv2 => '1234', #optional
+ );
+ $tx->submit();
+
+ if($tx->is_success()) {
+ print "Card processed successfully: ".$tx->authorization."\n";
+ } else {
+ print "Card was rejected: ".$tx->error_message."\n";
+ }
+
+=head1 DESCRIPTION
+
+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.
+
+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.
+
+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.
+
+Since the Virtual Merchant API is just a newer version of the viaKlix API, this module subclasses Business::OnlinePayment::viaKlix.
+
+This module does not use Elavon's XML encoding as this doesn't appear to offer any benefit over the standard encoding.
+
+=head1 SUBROUTINES
+
+=head2 set_defaults
+
+Sets defaults for the Virtual Merchant gateway URL.
+
+=cut
+
+sub set_defaults {
+ my $self = shift;
+ my %opts = @_;
+
+ $self->SUPER::set_defaults(%opts);
+ # standard B::OP methods/data
+ $self->server("www.myvirtualmerchant.com");
+ $self->port("443");
+ $self->path("/VirtualMerchant/process.do");
+
+}
+
+=head2 _map_fields
+
+Converts credit card types and transaction types from the Business::OnlinePayment values to Elavon's.
+
+=cut
+
+sub _map_fields {
+ my ($self) = @_;
+
+ my %content = $self->content();
+
+ #ACTION MAP
+ my %actions = (
+ 'normal authorization' => 'CCSALE', # Authorization/Settle transaction
+ 'credit' => 'CCCREDIT', # Credit (refund)
+ );
+
+ $content{'ssl_transaction_type'} = $actions{ lc( $content{'action'} ) }
+ || $content{'action'};
+
+ # TYPE MAP
+ my %types = (
+ 'visa' => 'CC',
+ 'mastercard' => 'CC',
+ 'american express' => 'CC',
+ 'discover' => 'CC',
+ 'cc' => 'CC',
+ );
+
+ $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
+
+ $self->transaction_type( $content{'type'} );
+
+ # stuff it back into %content
+ $self->content(%content);
+}
+
+=head2 submit
+
+Maps data from Business::OnlinePayment name space to Elavon's, checks that all required fields
+for the transaction type are present, and submits the transaction. Saves the results.
+
+=cut
+
+%maxlength = (
+ ssl_description => 255,
+ ssl_invoice_number => 25,
+ ssl_customer_code => 17,
+
+ ssl_first_name => 20,
+ ssl_last_name => 30,
+ ssl_company => 50,
+ ssl_avs_address => 30,
+ ssl_city => 30,
+ ssl_phone => 20,
+
+ ssl_ship_to_first_name => 20,
+ ssl_ship_to_last_name => 30,
+ ssl_ship_to_company => 50,
+ ssl_ship_to_address1 => 30,
+ ssl_ship_to_city => 30,
+ ssl_ship_to_phone => 20, #though we don't map anything to this...
+);
+
+sub submit {
+ my ($self) = @_;
+
+ $self->_map_fields();
+
+ my %content = $self->content;
+
+ my %required;
+ $required{CC_CCSALE} = [ qw( ssl_transaction_type ssl_merchant_id ssl_pin
+ ssl_amount ssl_card_number ssl_exp_date
+ ssl_cvv2cvc2_indicator
+ ) ];
+ $required{CC_CCCREDIT} = $required{CC_CCSALE};
+ my %optional;
+ $optional{CC_CCSALE} = [ qw( ssl_user_id ssl_salestax ssl_cvv2cvc2
+ ssl_description ssl_invoice_number
+ ssl_customer_code ssl_company ssl_first_name
+ ssl_last_name ssl_avs_address ssl_address2
+ ssl_city ssl_state ssl_avs_zip ssl_country
+ ssl_phone ssl_email ssl_ship_to_company
+ ssl_ship_to_first_name ssl_ship_to_last_name
+ ssl_ship_to_address1 ssl_ship_to_city
+ ssl_ship_to_state ssl_ship_to_zip
+ ssl_ship_to_country
+ ) ];
+ $optional{CC_CCCREDIT} = $optional{CC_CCSALE};
+
+ my $type_action = $self->transaction_type(). '_'. $content{ssl_transaction_type};
+ unless ( exists($required{$type_action}) ) {
+ $self->error_message("Elavon can't handle transaction type: ".
+ "$content{action} on " . $self->transaction_type() );
+ $self->is_success(0);
+ return;
+ }
+
+ my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
+ my $zip = $content{'zip'};
+ $zip =~ s/[^[:alnum:]]//g;
+
+ my $cvv2indicator = $content{"cvv2"} ? 1 : 9; # 1 = Present, 9 = Not Present
+
+ $self->_revmap_fields(
+
+ ssl_merchant_id => 'login',
+ ssl_pin => 'password',
+
+ ssl_amount => 'amount',
+ ssl_card_number => 'card_number',
+ ssl_exp_date => \$expdate_mmyy, # MMYY from 'expiration'
+ ssl_cvv2cvc2_indicator => \$cvv2indicator,
+ ssl_cvv2cvc2 => 'cvv2',
+ ssl_description => 'description',
+ ssl_invoice_number => 'invoice_number',
+ ssl_customer_code => 'customer_id',
+
+ ssl_first_name => 'first_name',
+ ssl_last_name => 'last_name',
+ ssl_company => 'company',
+ ssl_avs_address => 'address',
+ ssl_city => 'city',
+ ssl_state => 'state',
+ ssl_avs_zip => \$zip, # 'zip' with non-alnums removed
+ ssl_country => 'country',
+ ssl_phone => 'phone',
+ ssl_email => 'email',
+
+ ssl_ship_to_first_name => 'ship_first_name',
+ ssl_ship_to_last_name => 'ship_last_name',
+ ssl_ship_to_company => 'ship_company',
+ ssl_ship_to_address1 => 'ship_address',
+ ssl_ship_to_city => 'ship_city',
+ ssl_ship_to_state => 'ship_state',
+ ssl_ship_to_zip => 'ship_zip',
+ ssl_ship_to_country => 'ship_country',
+
+ );
+
+ my %params = $self->get_fields( @{$required{$type_action}},
+ @{$optional{$type_action}},
+ );
+
+ $params{$_} = substr($params{$_},0,$maxlength{$_})
+ foreach grep exists($maxlength{$_}), keys %params;
+
+ foreach ( keys ( %{($self->{_defaults})} ) ) {
+ $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_});
+ }
+
+ $params{ssl_test_mode}='true' if $self->test_transaction;
+
+ $params{ssl_show_form}='false';
+ $params{ssl_result_format}='ASCII';
+
+ $self->required_fields(@{$required{$type_action}});
+
+ warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $self->debug > 1;
+ my ( $page, $resp, %resp_headers ) =
+ $self->https_post( %params );
+
+ $self->response_code( $resp );
+ $self->response_page( $page );
+ $self->response_headers( \%resp_headers );
+
+ warn "$page\n" if $self->debug > 1;
+ # $page should contain key/value pairs
+
+ my $status ='';
+ my %results = map { s/\s*$//; split '=', $_, 2 } grep { /=/ } split '^', $page;
+
+ # AVS and CVS values may be set on success or failure
+ $self->avs_code( $results{ssl_avs_response} );
+ $self->cvv2_response( $results{ ssl_cvv2_response } );
+ $self->result_code( $status = $results{ errorCode } || $results{ ssl_result } );
+ $self->order_number( $results{ ssl_txn_id } );
+ $self->authorization( $results{ ssl_approval_code } );
+ $self->error_message( $results{ errorMessage } || $results{ ssl_result_message } );
+
+
+ if ( $resp =~ /^(HTTP\S+ )?200/ && $status eq "0" ) {
+ $self->is_success(1);
+ } else {
+ $self->is_success(0);
+ }
+}
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+Business::OnlinePayment, Business::OnlinePayment::viaKlix, Elavon Virtual Merchant Developers' Guide
+
+=head1 AUTHOR
+
+Richard Siddall, E<lt>elavon@elirion.netE<gt>
+
+=head1 BUGS
+
+Duplicates code to handle deprecated 'type' codes.
+
+Method for passing raw card track data is not documented by Elavon.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Richard Siddall. This module is largely based on Business::OnlinePayment::viaKlix by Jeff Finucane.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.8 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
--- /dev/null
+Changes
+Makefile.PL
+MANIFEST
+README
+t/00load.t
+t/bop.t
+t/credit_card.t
+t/live_card.t
+t/pod-coverage.t
+t/pod.t
+ElavonVirtualMerchant.pm
+META.yml Module meta-data (added by MakeMaker)
--- /dev/null
+# http://module-build.sourceforge.net/META-spec.html
+#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX#
+name: Business-OnlinePayment-ElavonVirtualMerchant
+version: 0.02
+version_from: ElavonVirtualMerchant.pm
+installdirs: site
+requires:
+ Business::OnlinePayment::viaKLIX:
+
+distribution_type: module
+generated_by: ExtUtils::MakeMaker version 6.30
--- /dev/null
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ NAME => 'Business::OnlinePayment::ElavonVirtualMerchant',
+ VERSION_FROM => 'ElavonVirtualMerchant.pm', # finds $VERSION
+ PREREQ_PM => {Business::OnlinePayment::viaKLIX},
+ ($] >= 5.005 ? ## Add these new keywords supported since 5.005
+ (ABSTRACT_FROM => 'ElavonVirtualMerchant.pm', # retrieve abstract from module
+ AUTHOR => 'Richard Siddall <elavon@elirion.net>') : ()),
+);
--- /dev/null
+Business-OnlinePayment-ElavonVirtualMerchant version 0.01
+=========================================================
+
+This module provides an interface to the Elavon (formerly Nova Information
+Systems) Virtual Merchant real-time payment gateway from applications
+using the Business::OnlinePayment API.
+
+INSTALLATION
+
+To install this module type the following:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+DEPENDENCIES
+
+This module requires these other modules and libraries:
+
+ Business::OnlinePayment::HTTPS
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2009 by Richard Siddall
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.8 or,
+at your option, any later version of Perl 5 you may have available.
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+BEGIN {
+ use_ok("Business::OnlinePayment")
+ or BAIL_OUT("unable to load Business::OnlinePayment\n");
+
+ use_ok("Business::OnlinePayment::ElavonVirtualMerchant")
+ or BAIL_OUT("unable to load Business::OnlinePayment::ElavonVirtualMerchant\n");
+}
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 11;
+
+use Business::OnlinePayment;
+
+my $package = "Business::OnlinePayment";
+my $driver = "ElavonVirtualMerchant";
+
+{ # new
+ my $obj;
+
+ $obj = $package->new($driver);
+ isa_ok( $obj, $package );
+
+ # convenience methods
+ can_ok( $obj, qw(order_number avs_code cvv2_response) );
+ can_ok( $obj, qw(debug expdate_mmyy) );
+
+ # internal methods
+ can_ok( $obj, qw(_map_fields _revmap_fields) );
+
+ # defaults
+ my $server = "www.myvirtualmerchant.com";
+
+ is( $obj->server, $server, "server($server)" );
+ is( $obj->port, "443", "port(443)" );
+ is( $obj->path, "/VirtualMerchant/process.do", "VirtualMerchant/process.do" );
+}
+
+{ # expdate
+ my $obj = $package->new($driver);
+ my @exp = (
+
+ #OFF [qw(1999.8 0899)],
+ #OFF [qw(1984-11 1184)],
+ #OFF [qw(06/7 0706)],
+ #OFF [qw(06-12 1206)],
+ [qw(12/06 1206)],
+ [qw(6/2000 0600)],
+ [qw(10/2000 1000)],
+ [qw(1/99 0199)],
+ );
+ foreach my $aref (@exp) {
+ my ( $exp, $moyr ) = @$aref;
+ my ($mmyy) = $obj->expdate_mmyy($exp);
+ is( $mmyy, $moyr, "$exp: MMYY '$mmyy' eq '$moyr' from $exp" );
+ }
+}
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More;
+
+use Business::OnlinePayment;
+
+my $runinfo =
+ "to test set environment variables:"
+ . " (required) ELAVON_ACCOUNT, ELAVON_USERID, and ELAVON_PASSWORD"
+ . " (optional) DEBUG, ELAVON_SERVER, and ELAVON_PATH";
+
+plan(
+ ( $ENV{"ELAVON_ACCOUNT"}
+ && $ENV{"ELAVON_USERID"}
+ && $ENV{"ELAVON_PASSWORD"} )
+ ? ( tests => 42 )
+ : ( skip_all => $runinfo )
+);
+
+my %opts = (
+ "debug" => $ENV{"DEBUG"} || 0,
+ "default_ssl_user_id" => $ENV{"ELAVON_USERID"},
+ "default_ssl_salestax" => "0.00",
+ "default_ssl_customer_code" => "TESTCUSTOMER",
+);
+
+my %content = (
+ login => $ENV{"ELAVON_ACCOUNT"},
+ password => $ENV{"ELAVON_PASSWORD"},
+ action => "Normal Authorization",
+ type => "VISA",
+ description => "Business::OnlinePayment::ElavonVirtualMerchant test",
+ card_number => "4111111111111111",
+ cvv2 => "123",
+ expiration => "12/" . strftime( "%y", localtime ),
+ amount => "0.01",
+ invoice_number => "Test1",
+ first_name => "Tofu",
+ last_name => "Beast",
+ email => 'nobody@example.com',
+ address => "123 Anystreet",
+ city => "Anywhere",
+ state => "GA",
+ zip => "30004",
+ country => "US",
+);
+
+{ # valid card number test
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content(%content);
+ tx_check(
+ $tx,
+ desc => "valid card_number",
+ is_success => 1,
+ result_code => "0",
+ authorization => "123456",
+ avs_code => "X",
+ cvv2_response => "P",
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+}
+
+{ # invalid card number test
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, card_number => "4111111111111112" );
+ tx_check(
+ $tx,
+ desc => "invalid card_number",
+ is_success => 0,
+ result_code => 5000,
+ authorization => undef,
+ avs_code => undef,
+ cvv2_response => undef,
+ order_number => undef,
+ );
+}
+
+
+SKIP: { # avs_code() / AVSZIP and AVSADDR tests
+
+ skip "AVS tests broken", 18;
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+
+ $tx->content( %content, "address" => "500 Any street" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=N,AVSZIP=Y",
+ is_success => 1,
+ result_code => "0",
+ authorization => "123456",
+ avs_code => "Z",
+ cvv2_response => "P",
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+
+ $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, "zip" => "99999" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=Y,AVSZIP=N",
+ is_success => 1,
+ result_code => "0",
+ authorization => "123456",
+ avs_code => "A",
+ cvv2_response => "P",
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+
+ $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, "address" => "500 Any street", "zip" => "99999" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=N,AVSZIP=N",
+ is_success => 1,
+ result_code => "0",
+ authorization => "123456",
+ avs_code => "N",
+ cvv2_response => "P",
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+}
+
+SKIP: { # cvv2_response() / CVV2MATCH
+
+ skip "CVV2 tests broken", 6;
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+
+ $tx->content( %content, "cvv2" => "301" );
+ tx_check(
+ $tx,
+ desc => "wrong cvv2",
+ is_success => 1,
+ result_code => "0",
+ authorization => "123456",
+ avs_code => "X",
+ cvv2_response => "N",
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+
+}
+
+SKIP: { # refund test
+
+ skip "credit/refund tests broken", 6;
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, 'action' => "Credit",
+ 'card_number' => "4444333322221111",
+ );
+ tx_check(
+ $tx,
+ desc => "refund/credit",
+ is_success => 1,
+ result_code => "0",
+ authorization => undef,
+ avs_code => undef,
+ cvv2_response => undef,
+ order_number => "00000000-0000-0000-0000-00000000000",
+ );
+}
+
+sub tx_check {
+ my $tx = shift;
+ my %o = @_;
+
+ $tx->test_transaction(1);
+ $tx->server($ENV{"ELAVON_SERVER"}) if defined($ENV{"ELAVON_SERVER"});
+ $tx->path($ENV{"ELAVON_PATH"}) if defined($ENV{"ELAVON_PATH"});
+ $tx->submit;
+
+ is( $tx->is_success, $o{is_success}, "$o{desc}: " . tx_info($tx) );
+ is( $tx->result_code, $o{result_code}, "result_code(): RESULT" );
+ is( $tx->authorization, $o{authorization}, "authorization() / AUTHCODE" );
+ is( $tx->avs_code, $o{avs_code}, "avs_code() / AVSADDR and AVSZIP" );
+ is( $tx->cvv2_response, $o{cvv2_response}, "cvv2_response() / CVV2MATCH" );
+ is( $tx->order_number, $o{order_number}, "order_number() / PNREF" );
+}
+
+sub tx_info {
+ my $tx = shift;
+
+ no warnings 'uninitialized';
+
+ return (
+ join( "",
+ "is_success(", $tx->is_success, ")",
+ " order_number(", $tx->order_number, ")",
+ " result_code(", $tx->result_code, ")",
+ " auth_info(", $tx->authorization, ")",
+ " avs_code(", $tx->avs_code, ")",
+ " cvv2_response(", $tx->cvv2_response, ")",
+ )
+ );
+}
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More;
+
+use Business::OnlinePayment;
+
+my $runinfo =
+ "to test set environment variables:"
+ . " (required) ELAVON_ACCOUNT, ELAVON_PASSWORD, ELAVON_CARD,"
+ . " ELAVON_CVV2, ELAVON_EXP, ELAVON_CARD_NAME, ELAVON_CARD_ADDRESS,"
+ . " ELAVON_CARD_CITY, ELAVON_CARD_STATE, ELAVON_CARD_ZIP, and ELAVON_DO_LIVE ";
+
+plan(
+ ( $ENV{"ELAVON_ACCOUNT"} && $ENV{"ELAVON_PASSWORD"} &&
+ $ENV{"ELAVON_CARD"} && $ENV{"ELAVON_CVV2"} &&
+ $ENV{"ELAVON_EXP"} && $ENV{"ELAVON_CARD_NAME"} &&
+ $ENV{"ELAVON_CARD_ADDRESS"} && $ENV{"ELAVON_CARD_CITY"} &&
+ $ENV{"ELAVON_CARD_STATE"} && $ENV{"ELAVON_CARD_ZIP"} &&
+ $ENV{"ELAVON_DO_LIVE"}
+ )
+ ? ( tests => 42 )
+ : ( skip_all => $runinfo )
+);
+
+my %opts = (
+ "debug" => 2,
+ "default_ssl_user_id" => $ENV{"ELAVON_USERID"},
+ "default_ssl_salestax" => "0.00",
+ "default_ssl_customer_code" => "LIVETESTCUSTOMER",
+);
+
+my %content = (
+ login => $ENV{"ELAVON_ACCOUNT"},
+ password => $ENV{"ELAVON_PASSWORD"},
+ action => "Normal Authorization",
+ type => "CC",
+ description => "Business::OnlinePayment::ElavonVirtualMerchant live test",
+ card_number => $ENV{"ELAVON_CARD"},
+ cvv2 => $ENV{"ELAVON_CVV2"},
+ expiration => $ENV{"ELAVON_EXP"},
+ amount => "0.01",
+ invoice_number => "LiveTest",
+ name => $ENV{"ELAVON_CARD_NAME"},
+ address => $ENV{"ELAVON_CARD_ADDRESS"},
+ city => $ENV{"ELAVON_CARD_CITY"},
+ state => $ENV{"ELAVON_CARD_STATE"},
+ zip => $ENV{"ELAVON_CARD_ZIP"},
+);
+
+my $credit_amount = 0;
+
+{ # valid card number test
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content(%content);
+ tx_check(
+ $tx,
+ desc => "valid card_number",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^\w{6}$/,
+ avs_code => "Y",
+ cvv2_response => "M",
+ order_number => qr/^([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})$/,
+ );
+ $credit_amount += $content{amount} if $tx->is_success;
+}
+
+{ # invalid card number test
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, card_number => "4111111111111112" );
+ tx_check(
+ $tx,
+ desc => "invalid card_number",
+ is_success => 0,
+ result_code => 4000,
+ authorization => qr/^$/,
+ avs_code => undef,
+ cvv2_response => undef,
+ order_number => qr/^$/,
+ );
+}
+
+
+{ # avs_code() / AVSZIP and AVSADDR tests
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+
+ $tx->content( %content, "address" => "500 Any street" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=N,AVSZIP=Y",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^\w{6}$/,
+ avs_code => "Z",
+ cvv2_response => "M",
+ order_number => qr/^([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})$/,
+ );
+ $credit_amount += $content{amount} if $tx->is_success;
+
+ $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, "zip" => "99999" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=Y,AVSZIP=N",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^\w{6}$/,
+ avs_code => "A",
+ cvv2_response => "M",
+ order_number => qr/^([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})$/,
+ );
+ $credit_amount += $content{amount} if $tx->is_success;
+
+ $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, "address" => "500 Any street", "zip" => "99999" );
+ tx_check(
+ $tx,
+ desc => "AVSADDR=N,AVSZIP=N",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^\w{6}$/,
+ avs_code => "N",
+ cvv2_response => "M",
+ order_number => qr/^([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})$/,
+ );
+ $credit_amount += $content{amount} if $tx->is_success;
+}
+
+{ # cvv2_response() / CVV2MATCH
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+
+ $tx->content( %content, "cvv2" => $content{cvv2}+1 );
+ tx_check(
+ $tx,
+ desc => "wrong cvv2",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^\w{6}$/,
+ avs_code => "Y",
+ cvv2_response => "N",
+ order_number => qr/^([0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12})$/,
+ );
+ $credit_amount += $content{amount} if $tx->is_success;
+
+}
+
+SKIP: { # refund test
+
+ skip "Refund tests require account with refund capability", 6;
+
+ my $tx = new Business::OnlinePayment( "ElavonVirtualMerchant", %opts );
+ $tx->content( %content, 'action' => "Credit",
+ 'amount' => sprintf("%.2f", $credit_amount),
+ );
+ tx_check(
+ $tx,
+ desc => "refund/credit",
+ is_success => 1,
+ result_code => "0",
+ authorization => qr/^$/,
+ avs_code => undef,
+ cvv2_response => undef,
+ order_number => qr/^$/,
+ );
+}
+
+sub tx_check {
+ my $tx = shift;
+ my %o = @_;
+
+ $tx->submit;
+
+ is( $tx->is_success, $o{is_success}, "$o{desc}: " . tx_info($tx) );
+ is( $tx->result_code, $o{result_code}, "result_code(): RESULT" );
+ like( $tx->authorization, $o{authorization}, "authorization() / AUTHCODE" );
+ is( $tx->avs_code, $o{avs_code}, "avs_code() / AVSADDR and AVSZIP" );
+ is( $tx->cvv2_response, $o{cvv2_response}, "cvv2_response() / CVV2MATCH" );
+ like( $tx->order_number, $o{order_number}, "order_number() / PNREF" );
+}
+
+sub tx_info {
+ my $tx = shift;
+
+ no warnings 'uninitialized';
+
+ return (
+ join( "",
+ "is_success(", $tx->is_success, ")",
+ " order_number(", $tx->order_number, ")",
+ " result_code(", $tx->result_code, ")",
+ " auth_info(", $tx->authorization, ")",
+ " avs_code(", $tx->avs_code, ")",
+ " cvv2_response(", $tx->cvv2_response, ")",
+ )
+ );
+}
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod::Coverage 1.00";
+plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage"
+ if $@;
+all_pod_coverage_ok({ also_private => [ qw( required_fields ) ]});
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod 1.00";
+plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+all_pod_files_ok();