From 39c43a68f35c5b18462471ef04104ced84554665 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 3 Nov 2007 01:27:17 +0000 Subject: [PATCH] Initial import --- Changes | 4 + MANIFEST | 12 ++ Makefile.PL | 12 ++ PlugnPay.pm | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 18 ++ t/00load.t | 13 ++ t/bop.t | 51 ++++++ t/credit_card.t | 164 +++++++++++++++++ t/live_card.t | 223 ++++++++++++++++++++++++ t/pod-coverage.t | 10 ++ t/pod.t | 9 + 11 files changed, 1038 insertions(+) create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 PlugnPay.pm 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..0f8ea84 --- /dev/null +++ b/Changes @@ -0,0 +1,4 @@ +0.01 Wed Oct 31 23:24:14 EDT 2007 + - original version; created by jeff +0.02 Wed Nov 2 1:48:42 EDT 2007 + - correct silly test bug diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..838d2fa --- /dev/null +++ b/MANIFEST @@ -0,0 +1,12 @@ +Changes +Makefile.PL +MANIFEST +README +PlugnPay.pm +t/00load.t +t/bop.t +t/credit_card.t +t/live_card.t +t/pod-coverage.t +t/pod.t +META.yml Module meta-data (added by MakeMaker) diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..3ee3fd5 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,12 @@ +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::PlugnPay', + 'VERSION_FROM' => 'PlugnPay.pm', # finds $VERSION + 'AUTHOR' => 'Jeff Finucane ', + 'PREREQ_PM' => { + 'Business::OnlinePayment' => 3, + 'Business::OnlinePayment::HTTPS' => 0.04, + }, +); diff --git a/PlugnPay.pm b/PlugnPay.pm new file mode 100644 index 0000000..47ba37d --- /dev/null +++ b/PlugnPay.pm @@ -0,0 +1,522 @@ +package Business::OnlinePayment::PlugnPay; + +use strict; +use vars qw($VERSION $DEBUG); +use Carp qw(carp croak); + +use base qw(Business::OnlinePayment::HTTPS); + +$VERSION = '0.02'; +$VERSION = eval $VERSION; +$DEBUG = 0; + +sub debug { + my $self = shift; + + if (@_) { + my $level = shift || 0; + if ( ref($self) ) { + $self->{"__DEBUG"} = $level; + } + else { + $DEBUG = $level; + } + $Business::OnlinePayment::HTTPS::DEBUG = $level; + } + return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG; +} + +sub set_defaults { + my $self = shift; + my %opts = @_; + + # standard B::OP methods/data + $self->server("pay1.plugnpay.com"); + $self->port("443"); + $self->path("/payment/pnpremote.cgi"); + + $self->build_subs(qw( + order_number avs_code cvv2_response + response_page response_code response_headers + )); + + # module specific data + if ( $opts{debug} ) { + $self->debug( $opts{debug} ); + delete $opts{debug}; + } + + my %_defaults = (); + foreach my $key (keys %opts) { + $key =~ /^default_(\w*)$/ or next; + $_defaults{$1} = $opts{$key}; + delete $opts{$key}; + } + $self->{_defaults} = \%_defaults; + +} + +sub _map_fields { + my ($self) = @_; + + my %content = $self->content(); + + #ACTION MAP + my %actions = ( + 'normal authorization' => 'auth', # Authorization/Settle transaction + 'credit' => 'newreturn',# Credit (refund) + 'void' => 'void', # Void + ); + + $content{'mode'} = $actions{ lc( $content{'action'} ) } + || $content{'action'}; + + # TYPE MAP + my %types = ( + 'visa' => 'CC', + 'mastercard' => 'CC', + 'american express' => 'CC', + 'discover' => 'CC', + 'cc' => 'CC', + 'check' => 'ECHECK', + ); + + $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'}; + + # PAYMETHOD MAP + my %paymethods = ( + 'CC' => 'credit', + 'ECHECK' => 'onlinecheck', + ); + + $content{'paymethod'} = $paymethods{ $content{'type'} }; + + $self->transaction_type( $content{'type'} ); + + # stuff it back into %content + $self->content(%content); +} + +sub _revmap_fields { + my ( $self, %map ) = @_; + my %content = $self->content(); + foreach ( keys %map ) { + $content{$_} = + ref( $map{$_} ) + ? ${ $map{$_} } + : $content{ $map{$_} }; + } + $self->content(%content); +} + +sub expdate_mmyy { + my $self = shift; + my $expiration = shift; + my $expdate_mmyy; + if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) { + my ( $month, $year ) = ( $1, $2 ); + $expdate_mmyy = sprintf( "%02d/", $month ) . $year; + } + return defined($expdate_mmyy) ? $expdate_mmyy : $expiration; +} + +sub required_fields { + my($self,@fields) = @_; + + my @missing; + my %content = $self->content(); + foreach(@fields) { + next + if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/); + push(@missing, $_); + } + + Carp::croak("missing required field(s): " . join(", ", @missing) . "\n") + if(@missing); + +} + +sub submit { + my ($self) = @_; + + die "Processor does not support a test mode" + if $self->test_transaction; + + $self->_map_fields(); + + my %content = $self->content; + + my %required; + $required{CC_auth} = [ qw( mode publisher-name card-amount card-name + card-number card-exp paymethod ) ]; + $required{CC_newreturn} = [ @{$required{CC_auth}}, qw( publisher-password ) ]; + $required{CC_void} = [ qw( mode publisher-name publisher-password orderID + card-amount ) ]; + #$required{ECHECK_auth} = [ qw( mode publisher-name accttype routingnum + # accountnum checknum paymethod ) ]; + my %optional; + $optional{CC_auth} = [ qw( publisher-email authtype required dontsndmail + easycard client convert cc-mail transflags + card-address1 card-address2 card-city card-state + card-prov card-zip card-country card-cvv + currency phone fax email shipinfo shipname + address1 address2 city state province zip + country ipaddress accttype orderID tax + shipping app-level order-id acct_code magstripe + marketdata carissuenum cardstartdate descrcodes + retailterms) ]; + $optional{CC_newreturn} = [ qw( orderID card-address1 card-address2 + card-city card-state card-zip card-country + notify-email + ) ]; + $optional{CC_void} = [ qw( notify-email ) ]; + + #$optional{ECHECK_auth} = $optional{CC_auth}; # ? + #$optional{ECHECK_newreturn} = $optional{CC_newreturn}; # ? legal combo? + #$optional{ECHECK_void} = $optional{CC_void}; # ? legal combo? + + my $type_action = $self->transaction_type(). '_'. $content{mode}; + unless ( exists($required{$type_action}) ) { + $self->error_message("plugnpay can't handle transaction type: ". + "$content{action} on " . $self->transaction_type() ); + $self->is_success(0); + return; + } + + my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} ); + + $self->_revmap_fields( + + 'publisher-name' => 'login', + 'publisher-password' => 'password', + + 'card-amount' => 'amount', + 'card-name' => 'name', + 'card-address1' => 'address', + 'card-city' => 'city', + 'card-state' => 'state', + 'card-zip' => 'zip', + 'card-country' => 'country', + 'card-number' => 'card_number', + 'card-exp' => \$expdate_mmyy, # MMYY from 'expiration' + 'card-cvv' => 'cvv2', + 'order-id' => 'invoice_number', + 'orderID' => 'order_number', + + + ); + + my %shipping_params = ( shipname => (($content{ship_first_name} || '') . + ' '. ($content{ship_last_name} || '')), + address1 => $content{ship_address}, + map { $_ => $content{ "ship_$_" } } + qw ( city state zip country ) + ); + + + foreach ( keys ( %shipping_params ) ) { + if ($shipping_params{$_} && $shipping_params{$_} =~ /^\s*$/) { + delete $shipping_params{$_}; + } + } + $shipping_params{shipinfo} = 1 if scalar(keys(%shipping_params)); + + my %params = ( $self->get_fields( @{$required{$type_action}}, + @{$optional{$type_action}}, + ), + (%shipping_params) + ); + + $params{'txn-type'} = 'auth' if $params{mode} eq 'void'; + + foreach ( keys ( %{($self->{_defaults})} ) ) { + $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_}); + } + + + $self->required_fields(@{$required{$type_action}}); + + warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $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 $DEBUG > 1; + # $page should contain key/value pairs + + my $status =''; + my %results = map { s/\s*$//; + my ($name, $value) = split '=', $_, 2; + $name =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; + $value =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; + $name, $value; + } split '&', $page; + + # AVS and CVS values may be set on success or failure + $self->avs_code( $results{ 'avs-code' } ); + $self->cvv2_response( $results{ cvvresp } ); + $self->result_code( $results{ 'resp-code' } ); + $self->order_number( $results{ orderID } ); + $self->authorization( $results{ 'auth-code' } ); + $self->error_message( $results{ MErrMsg } ); + + + if ( $resp =~ /^(HTTP\S+ )?200/ + &&($results{ FinalStatus } eq "success" || + $results{ FinalStatus } eq "pending" && $results{ mode } eq 'newreturn' + ) + ) { + $self->is_success(1); + } else { + $self->is_success(0); + } +} + +1; + +__END__ + +=head1 NAME + +Business::OnlinePayment::PlugnPay - plugnpay backend for Business::OnlinePayment + +=head1 SYNOPSIS + + use Business::OnlinePayment; + + my $tx = new Business::OnlinePayment( 'PlugnPay' ); + + # See the module documentation for details of content() + $tx->content( + type => 'CC', + action => 'Normal Authorization', + description => 'Business::OnlinePayment::plugnpay test', + amount => '49.95', + invoice_number => '100100', + customer_id => 'jef', + name => 'Jeff Finucane', + address => '123 Anystreet', + city => 'Anywhere', + state => 'GA', + zip => '30004', + email => 'plugnpay@weasellips.com', + card_number => '4111111111111111', + expiration => '12/09', + cvv2 => '123', + order_number => 'string', + ); + + $tx->submit(); + + if ( $tx->is_success() ) { + print( + "Card processed successfully: ", $tx->authorization, "\n", + "order number: ", $tx->order_number, "\n", + "CVV2 response: ", $tx->cvv2_response, "\n", + "AVS code: ", $tx->avs_code, "\n", + ); + } + else { + print( + "Card was rejected: ", $tx->error_message, "\n", + "order number: ", $tx->order_number, "\n", + ); + } + +=head1 DESCRIPTION + +This module is a back end driver that implements the interface +specified by L to support payment handling +via plugnpay's payment solution. + +See L for details on the interface this +modules supports. + +=head1 Standard methods + +=over 4 + +=item set_defaults() + +This method sets the 'server' attribute to 'pay1.plugnpay.com' and +the port attribute to '443'. This method also sets up the +L described below. + +=item submit() + +=back + +=head1 Unofficial methods + +This module provides the following methods which are not officially part of the +standard Business::OnlinePayment interface (as of 3.00_06) but are nevertheless +supported by multiple gateways modules and expected to be standardized soon: + +=over 4 + +=item L + +=item L + +=item L + +=back + +=head1 Module specific methods + +This module provides the following methods which are not currently +part of the standard Business::OnlinePayment interface: + +=over 4 + +=item L + +=item L + +=back + +=head1 Settings + +The following default settings exist: + +=over 4 + +=item server + +pay1.plugnpay.com + +=item port + +443 + +=item path + +/payment/pnpremote.cgi + +=back + +=head1 Parameters passed to constructor + +If any of the key/value pairs passed to the constructor have a key +beginning with "default_" then those values are passed to plugnpay as +a the corresponding form field (without the "default_") whenever +content(%content) lacks that key. + +=head1 Handling of content(%content) + +The following rules apply to content(%content) data: + +=head2 type + +If 'type' matches one of the following keys it is replaced by the +right hand side value: + + 'visa' => 'CC', + 'mastercard' => 'CC', + 'american express' => 'CC', + 'discover' => 'CC', + +The value of 'type' is used to set transaction_type(). Currently this +module only supports the above values. + +=head1 Setting plugnpay parameters from content(%content) + +The following rules are applied to map data to plugnpay parameters +from content(%content): + + # plugnpay param => $content{} + publisher-name => 'login', + publisher-password => 'password', + + card-amount => 'amount', + card-number => 'card_number', + card-exp => \( $month.$year ), # MM/YY from 'expiration' + ssl_cvv => 'cvv2', + order-id => 'invoice_number', + + card-name => 'name', + card-address1 => 'address', + card-city => 'city', + card-state => 'state', + card-zip => 'zip' + card-country => 'country', + orderID => 'order_number' # can be set via order_number() + + shipname => 'ship_first_name' . ' ' . 'ship_last_name', + address1 => 'ship_address', + city => 'ship_city', + state => 'ship_state', + zip => 'ship_zip', + country => 'ship_country', + + +=head1 Mapping plugnpay transaction responses to object methods + +The following methods provides access to the transaction response data +resulting from a plugnpay request (after submit()) is called: + +=head2 order_number() + +This order_number() method returns the orderID field for transactions +to uniquely identify the transaction. + +=head2 result_code() + +The result_code() method returns the resp-code field for transactions. +It is the alphanumeric return code indicating the outcome of the attempted +transaction. + +=head2 error_message() + +The error_message() method returns the MErrMsg field for transactions. +This provides more details about the transaction result. + +=head2 authorization() + +The authorization() method returns the auth-code field, +which is the approval code obtained from the card processing network. + +=head2 avs_code() + +The avs_code() method returns the avs-code field from the transaction result. + +=head2 cvv2_response() + +The cvv2_response() method returns the cvvresp field, which is a +response message returned with the transaction result. + +=head2 expdate_mmyy() + +The expdate_mmyy() method takes a single scalar argument (typically +the value in $content{expiration}) and attempts to parse and format +and put the date in MM/YY format as required by the plugnpay +specification. If unable to parse the expiration date simply leave it +as is and let the plugnpay system attempt to handle it as-is. + +=head2 debug() + +Enable or disble debugging. The value specified here will also set +$Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in +troubleshooting problems. + +=head1 COMPATIBILITY + +This module implements an interface to the plugnpay Remote Client Integration +Specification Rev. 10.03.2007 + +=head1 AUTHORS + +Jeff Finucane + +Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler +and Phil Lobbes. + +=head1 SEE ALSO + +perl(1), L, L, and the Remote Client Integration +Specification from plugnpay. + +=cut diff --git a/README b/README new file mode 100644 index 0000000..aa02d8b --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +Copyright (c) 2007 Jeff Finucane +Copyright (c) 2007 Freeside Internet Services, Inc. +All rights reserved. This program is free software; you can +redistribute it and/or modify it under the same terms as Perl itself. + +This is Business::OnlinePayment::PlugnPay, a Business::OnlinePayment +back end module for plugnpay. http://www.plugnplay.com/ + +Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles. +Currently maintained by Jeff Finucane . + +Business::OnlinePayment is a generic interface for processing payments +through online credit card processors, online check acceptance houses, +etc. (If you like buzzwords, call it an "multiplatform +ecommerce-enabling middleware solution"). + +Check support is in "developer mode" because I have no test facilities. +A developer should easily be able to get check support functioning. diff --git a/t/00load.t b/t/00load.t new file mode 100644 index 0000000..89ad990 --- /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::PlugnPay") + or BAIL_OUT("unable to load Business::OnlinePayment::PlugnPay\n"); +} diff --git a/t/bop.t b/t/bop.t new file mode 100644 index 0000000..9447604 --- /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 = "PlugnPay"; + +{ # 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 = "pay1.plugnpay.com"; + + is( $obj->server, $server, "server($server)" ); + is( $obj->port, "443", "port(443)" ); + is( $obj->path, "/payment/pnpremote.cgi", "pnpremote.cgi" ); +} + +{ # expdate + my $obj = $package->new($driver); + my @exp = ( + + #OFF [qw(1999.8 08/99)], + #OFF [qw(1984-11 11/84)], + #OFF [qw(06/7 07/06)], + #OFF [qw(06-12 12/06)], + [qw(12/06 12/06)], + [qw(6/2000 06/00)], + [qw(10/2000 10/00)], + [qw(1/99 01/99)], + ); + 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..1fc7615 --- /dev/null +++ b/t/credit_card.t @@ -0,0 +1,164 @@ +#!/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) PNP_ACCOUNT and PNP_PASSWORD"; + +plan( + ( $ENV{"PNP_ACCOUNT"} + && $ENV{"PNP_PASSWORD"} ) + ? ( tests => 30 ) + : ( skip_all => $runinfo ) +); + +my %opts = ( + "debug" => 0, +); + +my %content = ( + login => $ENV{"PNP_ACCOUNT"}, + password => $ENV{"PNP_PASSWORD"}, + action => "Normal Authorization", + type => "VISA", + description => "Business::OnlinePayment::PlugnPay test", + card_number => "4111111111111111", + expiration => "12/" . strftime( "%y", localtime ), + amount => "0.01", + name => "cardtest", + cvv2 => "123", + invoice_number => "Test1", + email => 'plugnpay@weasellips.com', + address => "123 Anystreet", + city => "Anywhere", + state => "GA", + zip => "30004", + country => "US", + ship_first_name=> "Tofu", + ship_last_name => "Beast", + ship_address => "456 Anystreet", + ship_city => "Somewhere", + ship_state => "CA", + ship_zip => "90004", + ship_country => "US", +); + +{ # valid card number test + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content(%content); + tx_check( + $tx, + desc => "valid card_number", + is_success => 1, + result_code => "00", + authorization => "TSTAUT", + avs_code => "U", + cvv2_response => "M", + order_number => qr/^([0-9]{19})$/, + ); +} + +{ # invalid card number test + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, card_number => "4111111111111112" ); + tx_check( + $tx, + desc => "invalid card_number", + is_success => 0, + result_code => "P66", + authorization => "", + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + +{ # dubious faked bad card test + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, amount => "1000.01" ); + tx_check( + $tx, + desc => "faked bad card", + is_success => 0, + result_code => "P30", + authorization => "", + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + +{ # dubious faked problem test + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, amount => "2000.01" ); + tx_check( + $tx, + desc => "faked problem", + is_success => 0, + result_code => "P35", + authorization => "", + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + + +SKIP: { # refund test + + skip "credit/refund tests broken", 6; + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, 'action' => "Credit", + ); + tx_check( + $tx, + desc => "refund/credit", + is_success => 0, # :\ + result_code => undef, + authorization => undef, + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + +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" ); + 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" ); + 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/live_card.t b/t/live_card.t new file mode 100644 index 0000000..21a49c4 --- /dev/null +++ b/t/live_card.t @@ -0,0 +1,223 @@ +#!/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) PNP_ACCOUNT, PNP_PASSWORD, PNP_CARD," + . " PNP_CVV2, PNP_EXP, PNP_CARD_NAME, PNP_CARD_ADDRESS," + . " PNP_CARD_CITY, PNP_CARD_STATE, PNP_CARD_ZIP, and PNP_DO_LIVE "; + +plan( + ( $ENV{"PNP_ACCOUNT"} && $ENV{"PNP_PASSWORD"} && + $ENV{"PNP_CARD"} && $ENV{"PNP_CVV2"} && + $ENV{"PNP_EXP"} && $ENV{"PNP_CARD_NAME"} && + $ENV{"PNP_CARD_ADDRESS"} && $ENV{"PNP_CARD_CITY"} && + $ENV{"PNP_CARD_STATE"} && $ENV{"PNP_CARD_ZIP"} && + $ENV{"PNP_DO_LIVE"} + ) + ? ( tests => 48 ) + : ( skip_all => $runinfo ) +); + +my %opts = ( + "debug" => 0, +); + +my %content = ( + login => $ENV{"PNP_ACCOUNT"}, + password => $ENV{"PNP_PASSWORD"}, + action => "Normal Authorization", + type => "CC", + description => "Business::OnlinePayment::PlugnPay live test", + card_number => $ENV{"PNP_CARD"}, + cvv2 => $ENV{"PNP_CVV2"}, + expiration => $ENV{"PNP_EXP"}, + amount => "0.01", + invoice_number => "LiveTest", + name => $ENV{"PNP_CARD_NAME"}, + address => $ENV{"PNP_CARD_ADDRESS"}, + city => $ENV{"PNP_CARD_CITY"}, + state => $ENV{"PNP_CARD_STATE"}, + zip => $ENV{"PNP_CARD_ZIP"}, +); + +my $voidable; +my $voidable_amount = 0; +my $credit_amount = 0; + +{ # valid card number test + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content(%content); + tx_check( + $tx, + desc => "valid card_number", + is_success => 1, + result_code => "A", # wtf? + authorization => qr/^\w{6}$/, + avs_code => "Y", + cvv2_response => "M", + order_number => qr/^([0-9]{19})$/, + ); + $voidable = $tx->order_number if $tx->is_success; + $voidable_amount = $content{amount} if $tx->is_success; +} + +{ # invalid card number test + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, card_number => "4111111111111112" ); + tx_check( + $tx, + desc => "invalid card_number", + is_success => 0, + result_code => "P66", + authorization => qr/^$/, + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + + +{ # avs_code() / AVSZIP and AVSADDR tests + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + + $tx->content( %content, "address" => "500 Any street" ); + tx_check( + $tx, + desc => "AVSADDR=N,AVSZIP=Y", + is_success => 1, + result_code => "A", + authorization => qr/^\w{6}$/, + avs_code => "Z", + cvv2_response => "M", + order_number => qr/^([0-9]{19})$/, + ); + $credit_amount += $content{amount} if $tx->is_success; + + $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, "zip" => "99999" ); + tx_check( + $tx, + desc => "AVSADDR=Y,AVSZIP=N", + is_success => 1, + result_code => "A", + authorization => qr/^\w{6}$/, + avs_code => "A", + cvv2_response => "M", + order_number => qr/^([0-9]{19})$/, + ); + $credit_amount += $content{amount} if $tx->is_success; + + $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, "address" => "500 Any street", "zip" => "99999" ); + tx_check( + $tx, + desc => "AVSADDR=N,AVSZIP=N", + is_success => 1, + result_code => "A", + authorization => qr/^\w{6}$/, + avs_code => "N", + cvv2_response => "M", + order_number => qr/^([0-9]{19})$/, + ); + $credit_amount += $content{amount} if $tx->is_success; +} + +{ # cvv2_response() / CVV2MATCH + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + + $tx->content( %content, "cvv2" => $content{cvv2}+1 ); + tx_check( + $tx, + desc => "wrong cvv2", + is_success => 0, + result_code => "P02", # configurable? + authorization => qr/^\w{6}$/, + avs_code => "Y", + cvv2_response => "N", + order_number => qr/^([0-9]{19})$/, + ); + #$credit_amount += $content{amount} if $tx->is_success; + +} + +SKIP: { # void test + + #skip "Void tests require account with void capability", 6; + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, 'action' => "Void", + 'order_number' => $voidable, + ); + tx_check( + $tx, + desc => "void", + is_success => 1, + result_code => undef, + authorization => qr/^$/, + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); + $credit_amount += $voidable_amount unless $tx->is_success; +} + +SKIP: { # refund test + + #skip "Refund tests require account with refund capability", 6; + + my $tx = new Business::OnlinePayment( "PlugnPay", %opts ); + $tx->content( %content, 'action' => "Credit", + 'amount' => sprintf("%.2f", $credit_amount), + ); + tx_check( + $tx, + desc => "refund/credit", + is_success => 1, + result_code => undef, + authorization => qr/^$/, + avs_code => undef, + cvv2_response => undef, + order_number => qr/^([0-9]{19})$/, + ); +} + +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