--- /dev/null
+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
--- /dev/null
+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)
--- /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::PlugnPay',
+ 'VERSION_FROM' => 'PlugnPay.pm', # finds $VERSION
+ 'AUTHOR' => 'Jeff Finucane <jeff@cmh.net>',
+ 'PREREQ_PM' => {
+ 'Business::OnlinePayment' => 3,
+ 'Business::OnlinePayment::HTTPS' => 0.04,
+ },
+);
--- /dev/null
+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<Business::OnlinePayment> to support payment handling
+via plugnpay's payment solution.
+
+See L<Business::OnlinePayment> 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</Module specific methods> 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<order_number()|/order_number()>
+
+=item L<avs_code()|/avs_code()>
+
+=item L<cvv2_response()|/cvv2_response()>
+
+=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<expdate_mmyy()|/expdate_mmyy()>
+
+=item L<debug()|/debug()>
+
+=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{<key>}
+ 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 <plugnpay@weasellips.com>
+
+Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler
+and Phil Lobbes.
+
+=head1 SEE ALSO
+
+perl(1), L<Business::OnlinePayment>, L<Carp>, and the Remote Client Integration
+Specification from plugnpay.
+
+=cut
--- /dev/null
+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 <plugnpay@weasellips.com>.
+
+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.
--- /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::PlugnPay")
+ or BAIL_OUT("unable to load Business::OnlinePayment::PlugnPay\n");
+}
--- /dev/null
+#!/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" );
+ }
+}
--- /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) 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, ")",
+ )
+ );
+}
--- /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) 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, ")",
+ )
+ );
+}
--- /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();