From 14932f12c139b947eab50fa0880b83a9b7f2cb94 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Fri, 1 Apr 2016 14:25:19 -0700 Subject: [PATCH] initial checkin --- Changes | 13 +++ ElavonVirtualMerchant.pm | 293 +++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST | 12 ++ META.yml | 11 ++ Makefile.PL | 11 ++ README | 30 +++++ t/00load.t | 13 +++ t/bop.t | 51 +++++++++ t/credit_card.t | 200 ++++++++++++++++++++++++++++++++ t/live_card.t | 202 ++++++++++++++++++++++++++++++++ t/pod-coverage.t | 10 ++ t/pod.t | 9 ++ 12 files changed, 855 insertions(+) create mode 100644 Changes create mode 100644 ElavonVirtualMerchant.pm create mode 100644 MANIFEST create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100644 t/00load.t create mode 100644 t/bop.t create mode 100644 t/credit_card.t create mode 100644 t/live_card.t create mode 100644 t/pod-coverage.t create mode 100644 t/pod.t diff --git a/Changes b/Changes new file mode 100644 index 0000000..14bd560 --- /dev/null +++ b/Changes @@ -0,0 +1,13 @@ +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 + diff --git a/ElavonVirtualMerchant.pm b/ElavonVirtualMerchant.pm new file mode 100644 index 0000000..438c696 --- /dev/null +++ b/ElavonVirtualMerchant.pm @@ -0,0 +1,293 @@ +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, Eelavon@elirion.netE + +=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 + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..77ecc05 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,12 @@ +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) diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..3bcd94c --- /dev/null +++ b/META.yml @@ -0,0 +1,11 @@ +# 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 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..490fcea --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,11 @@ +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 ') : ()), +); diff --git a/README b/README new file mode 100644 index 0000000..1e69ae3 --- /dev/null +++ b/README @@ -0,0 +1,30 @@ +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. + diff --git a/t/00load.t b/t/00load.t new file mode 100644 index 0000000..92d3398 --- /dev/null +++ b/t/00load.t @@ -0,0 +1,13 @@ +#!/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"); +} diff --git a/t/bop.t b/t/bop.t new file mode 100644 index 0000000..daf9241 --- /dev/null +++ b/t/bop.t @@ -0,0 +1,51 @@ +#!/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" ); + } +} diff --git a/t/credit_card.t b/t/credit_card.t new file mode 100644 index 0000000..e271ece --- /dev/null +++ b/t/credit_card.t @@ -0,0 +1,200 @@ +#!/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, ")", + ) + ); +} diff --git a/t/live_card.t b/t/live_card.t new file mode 100644 index 0000000..293636f --- /dev/null +++ b/t/live_card.t @@ -0,0 +1,202 @@ +#!/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, ")", + ) + ); +} diff --git a/t/pod-coverage.t b/t/pod-coverage.t new file mode 100644 index 0000000..640ed2c --- /dev/null +++ b/t/pod-coverage.t @@ -0,0 +1,10 @@ +#!/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 ) ]}); diff --git a/t/pod.t b/t/pod.t new file mode 100644 index 0000000..2c9935c --- /dev/null +++ b/t/pod.t @@ -0,0 +1,9 @@ +#!/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(); -- 2.11.0