initial checkin
authorIvan Kohler <ivan@freeside.biz>
Fri, 1 Apr 2016 21:25:19 +0000 (14:25 -0700)
committerIvan Kohler <ivan@freeside.biz>
Fri, 1 Apr 2016 21:25:19 +0000 (14:25 -0700)
12 files changed:
Changes [new file with mode: 0644]
ElavonVirtualMerchant.pm [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
META.yml [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
README [new file with mode: 0644]
t/00load.t [new file with mode: 0644]
t/bop.t [new file with mode: 0644]
t/credit_card.t [new file with mode: 0644]
t/live_card.t [new file with mode: 0644]
t/pod-coverage.t [new file with mode: 0644]
t/pod.t [new file with mode: 0644]

diff --git a/Changes b/Changes
new file mode 100644 (file)
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 (file)
index 0000000..438c696
--- /dev/null
@@ -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, 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
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..490fcea
--- /dev/null
@@ -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 <elavon@elirion.net>') : ()),
+);
diff --git a/README b/README
new file mode 100644 (file)
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 (file)
index 0000000..92d3398
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..e271ece
--- /dev/null
@@ -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 (file)
index 0000000..293636f
--- /dev/null
@@ -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 (file)
index 0000000..640ed2c
--- /dev/null
@@ -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 (file)
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();