From 0269ed0cf31fa866f4413dd81b7ca0cdaeee41d7 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 24 Jun 2008 02:10:41 +0000 Subject: [PATCH] initial import --- Changes | 5 + IPPay.pm | 524 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST | 10 ++ META.yml | 21 +++ Makefile.PL | 19 +++ README | 19 +++ t/00load.t | 5 + t/bop.t | 5 + t/card.t | 184 +++++++++++++++++++++ t/check.t | 99 ++++++++++++ 10 files changed, 891 insertions(+) create mode 100644 Changes create mode 100644 IPPay.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/card.t create mode 100644 t/check.t diff --git a/Changes b/Changes new file mode 100644 index 0000000..9e1a7d2 --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for Perl extension Business::OnlinePayment::IPPay. + +0.01 Sun Jun 22 17:56:23 2008 EDT + - original version; + diff --git a/IPPay.pm b/IPPay.pm new file mode 100644 index 0000000..d9a0624 --- /dev/null +++ b/IPPay.pm @@ -0,0 +1,524 @@ +package Business::OnlinePayment::IPPay; + +use strict; +use Carp; +use Tie::IxHash; +use XML::Simple; +use XML::Writer; +use LWP::UserAgent; +use HTTP::Request; +use HTTP::Request::Common qw (POST); +use Date::Calc qw(Add_Delta_YM Add_Delta_Days); +use Business::OnlinePayment; +use Business::OnlinePayment::HTTPS; +use vars qw($VERSION $DEBUG @ISA $me); + +@ISA = qw(Business::OnlinePayment::HTTPS); +$VERSION = '0.01'; +$DEBUG = 1; +$me = 'Business::OnlinePayment::IPPay'; + +sub set_defaults { + my $self = shift; + my %opts = @_; + + # standard B::OP methods/data + $self->server('test1.jetpay.com') unless $self->server; + $self->port('443') unless $self->port; + $self->path('/jetpay') unless $self->path; + + $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(); + + # TYPE MAP + my %types = ( 'visa' => 'CC', + 'mastercard' => 'CC', + 'american express' => 'CC', + 'discover' => 'CC', + 'check' => 'ECHECK', + ); + $content{'type'} = $types{lc($content{'type'})} || $content{'type'}; + $self->transaction_type($content{'type'}); + + # ACTION MAP + my $action = lc($content{'action'}); + my %actions = + ( 'normal authorization' => 'SALE', + 'authorization only' => 'AUTHONLY', + 'post authorization' => 'CAPT', + 'void' => 'VOID', + 'credit' => 'CREDIT', + ); + my %check_actions = + ( 'normal authorization' => 'CHECK', + 'void' => 'VOIDACH', + 'credit' => 'REVERSAL', + ); + if ($self->transaction_type eq 'CC') { + $content{'TransactionType'} = $actions{$action} || $action; + }elsif ($self->transaction_type eq 'ECHECK') { + $content{'TransactionType'} = $check_actions{$action} || $action; + } + + + # ACCOUNT TYPE MAP + my %account_types = ('personal checking' => 'Checking', + 'personal savings' => 'Savings', + 'business checking' => 'BusinessCk', + ); + $content{'account_type'} = $account_types{lc($content{'account_type'})} + || $content{'account_type'}; + + $content{Origin} = 'RECURRING' + if ($content{recurring_billing} &&$content{recurring_billing} eq 'YES' ); + + # stuff it back into %content + $self->content(%content); + +} + +sub expdate_month { + my ($self, $exp) = (shift, shift); + my $month; + if ( defined($exp) and $exp =~ /^(\d+)\D+\d*\d{2}$/ ) { + $month = sprintf( "%02d", $1 ); + }elsif ( defined($exp) and $exp =~ /^(\d{2})\d{2}$/ ) { + $month = sprintf( "%02d", $1 ); + } + return $month; +} + +sub expdate_year { + my ($self, $exp) = (shift, shift); + my $year; + if ( defined($exp) and $exp =~ /^\d+\D+\d*(\d{2})$/ ) { + $year = sprintf( "%02d", $1 ); + }elsif ( defined($exp) and $exp =~ /^\d{2}(\d{2})$/ ) { + $year = sprintf( "%02d", $1 ); + } + return $year; +} + +sub revmap_fields { + my $self = shift; + tie my(%map), 'Tie::IxHash', @_; + my %content = $self->content(); + map { + my $value; + if ( ref( $map{$_} ) eq 'HASH' ) { + $value = $map{$_} if ( keys %{ $map{$_} } ); + }elsif( ref( $map{$_} ) ) { + $value = ${ $map{$_} }; + }elsif( exists( $content{ $map{$_} } ) ) { + $value = $content{ $map{$_} }; + } + + if (defined($value)) { + ($_ => $value); + }else{ + (); + } + } (keys %map); +} + +sub submit { + my($self) = @_; + + $self->is_success(0); + $self->map_fields(); + + my @required_fields = qw(action login type); + + my $action = lc($self->{_content}->{action}); + my $type = $self->transaction_type(); + if ( $action eq 'normal authorization' + || $action eq 'credit' + || $action eq 'authorization only' && $type eq 'CC') + { + push @required_fields, qw( amount ); + + push @required_fields, qw( card_number expiration ) + if ($type eq "CC"); + + push @required_fields, + qw( routing_code account_number name ) # account_type + if ($type eq "ECHECK"); + + }elsif ( $action eq 'post authorization' && $type eq 'CC') { + push @required_fields, qw( order_number ); + }elsif ( $action eq 'void') { + push @required_fields, qw( order_number amount ); + + push @required_fields, qw( authorization card_number ) + if ($type eq "CC"); + + push @required_fields, + qw( routing_code account_number name ) # account_type + if ($type eq "ECHECK"); + + }else{ + croak "$me can't handle transaction type: ". + $self->{_content}->{action}. " for ". + $self->transaction_type(); + } + + my %content = $self->content(); + foreach ( keys ( %{($self->{_defaults})} ) ) { + $content{$_} = $self->{_defaults}->{$_} unless exists($content{$_}); + } + $self->content(%content); + + $self->required_fields(@required_fields); + + my $transaction_id = $content{'order_number'}; + unless ($transaction_id) { + my ($page, $server_response, %headers) = $self->https_get('dummy' => 1); + return unless $server_response=~ /^200/; + $transaction_id = $page; + } + + my $cardexpmonth = $self->expdate_month($content{expiration}); + my $cardexpyear = $self->expdate_year($content{expiration}); + my $cardstartmonth = $self->expdate_month($content{card_start}); + my $cardstartyear = $self->expdate_year($content{card_start}); + + my $amount; + if (defined($content{amount})) { + $amount = sprintf("%.2f", $content{amount}); + $amount =~ s/\.//; + } + + my $check_number = $content{check_number} || "100" # make one up + if($content{account_number}); + + my $terminalid = $content{login} if $type eq 'CC'; + my $merchantid = $content{login} if $type eq 'ECHECK'; + + tie my %ach, 'Tie::IxHash', + $self->revmap_fields( + #AccountType => 'account_type', + AccountNumber => 'account_number', + ABA => 'routing_code', + CheckNumber => \$check_number, + ); + + tie my %industryinfo, 'Tie::IxHash', + $self->revmap_fields( + Type => 'IndustryInfo', + ); + + tie my %shippingaddr, 'Tie::IxHash', + $self->revmap_fields( + Address => 'ship_address', + City => 'ship_city', + StateProv => 'ship_state', + Country => 'ship_country', + Phone => 'ship_phone', + ); + + tie my %shippinginfo, 'Tie::IxHash', + $self->revmap_fields( + CustomerPO => 'CustomerPO', + ShippingMethod => 'ShippingMethod', + ShippingName => 'ship_name', + ShippingAddr => \%shippingaddr, + ); + + tie my %req, 'Tie::IxHash', + $self->revmap_fields( + TransactionType => 'TransactionType', + TerminalID => 'login', +# TerminalID => \$terminalid, +# MerchantID => \$merchantid, + TransactionID => \$transaction_id, + RoutingCode => 'RoutingCode', + Approval => 'authorization', + BatchID => 'BatchID', + Origin => 'Origin', + Password => 'password', + OrderNumber => 'invoice_number', + CardNum => 'card_number', + CVV2 => 'cvv2', + Issue => 'issue_number', + CardExpMonth => \$cardexpmonth, + CardExpYear => \$cardexpyear, + CardStartMonth => \$cardstartmonth, + CardStartYear => \$cardstartyear, + Track1 => 'track1', + Track2 => 'track2', + ACH => \%ach, + CardName => 'name', + DispositionType => 'DispositionType', + TotalAmount => \$amount, + FeeAmount => 'FeeAmount', + TaxAmount => 'TaxAmount', + BillingAddress => 'address', + BillingCity => 'city', + BillingStateProv => 'state', + BillingPostalCode => 'zip', + BillingCountry => 'country', + BillingPhone => 'phone', + Email => 'email', + UserIPAddr => 'customer_ip', + UserHost => 'UserHost', + UDField1 => 'UDField1', + UDField2 => 'UDField2', + UDField3 => 'UDField3', + ActionCode => 'ActionCode', + IndustryInfo => \%industryinfo, + ShippingInfo => \%shippinginfo, + ); + + my $post_data; + my $writer = new XML::Writer( OUTPUT => \$post_data, + DATA_MODE => 1, + DATA_INDENT => 1, + ENCODING => 'us-ascii', + ); + $writer->xmlDecl(); + $writer->startTag('JetPay'); + foreach ( keys ( %req ) ) { + $self->_xmlwrite($writer, $_, $req{$_}); + } + $writer->endTag('JetPay'); + $writer->end(); + + if ($self->test_transaction()) { + $self->server('test1.jetpay.com'); + $self->port('443'); + $self->path('/jetpay'); + } + + warn "$post_data\n" if $DEBUG; + + my ($page,$server_response,%headers) = $self->https_post($post_data); + + warn "$page\n" if $DEBUG; + + my $response = {}; + if ($server_response =~ /^200/){ + $response = XMLin($page); + if ( exists($response->{ActionCode}) && !exists($response->{ErrMsg})) { + $self->error_message($response->{ResponseText}); + }else{ + $self->error_message($response->{Errmsg}); + } +# }else{ +# $self->error_message("Server Failed"); + } + + $self->result_code($response->{ActionCode} || ''); + $self->order_number($response->{TransactionID} || ''); + $self->authorization($response->{Approval} || ''); + $self->cvv2_response($response->{CVV2} || ''); + $self->avs_code($response->{AVS} || ''); + + $self->is_success($self->result_code() eq '000' ? 1 : 0); + + unless ($self->is_success()) { + unless ( $self->error_message() ) { #additional logging information + $self->error_message( + "(HTTPS response: $server_response) ". + "(HTTPS headers: ". + join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ". + "(Raw HTTPS content: $page)" + ); + } + } + +} + +sub _xmlwrite { + my ($self, $writer, $item, $value) = @_; + $writer->startTag($item); + if ( ref( $value ) eq 'HASH' ) { + foreach ( keys ( %$value ) ) { + $self->_xmlwrite($writer, $_, $value->{$_}); + } + }else{ + $writer->characters($value); + } + $writer->endTag($item); +} + +1; +__END__ + +=head1 NAME + +Business::OnlinePayment::IPPay - IPPay backend for Business::OnlinePayment + +=head1 SYNOPSIS + + use Business::OnlinePayment; + + my $tx = + new Business::OnlinePayment( "IPPay", + 'default_Origin' => 'PHONE ORDER', + ); + $tx->content( + type => 'VISA', + login => 'testdrive', + password => '', #password + action => 'Normal Authorization', + description => 'Business::OnlinePayment test', + amount => '49.95', + customer_id => 'tfb', + name => 'Tofu Beast', + 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 SUPPORTED TRANSACTION TYPES + +=head2 CC, Visa, MasterCard, American Express, Discover + +Content required: type, login, action, amount, card_number, expiration. + +=head2 Check + +Content required: type, login, action, amount, name, account_number, routing_code. + +=head1 DESCRIPTION + +For detailed information see L. + +=head1 METHODS AND FUNCTIONS + +See L for the complete list. The following methods either override the methods in L or provide additional functions. + +=head2 result_code + +Returns the response error code. + +=head2 error_message + +Returns the response error description text. + +=head2 server_response + +Returns the complete response from the server. + +=head1 Handling of content(%content) data: + +=head2 action + +The following actions are valid + + normal authorization + authorization only + post authorization + credit + void + +=head1 Setting IPPay parameters from content(%content) + +The following rules are applied to map data to IPPay parameters +from content(%content): + + # param => $content{} + TransactionType => 'TransactionType', + TerminalID => 'login', + TransactionID => 'order_number', + RoutingCode => 'RoutingCode', + Approval => 'authorization', + BatchID => 'BatchID', + Origin => 'Origin', + Password => 'password', + OrderNumber => 'invoice_number', + CardNum => 'card_number', + CVV2 => 'cvv2', + Issue => 'issue_number', + CardExpMonth => \( $month ), # MM from MM(-)YY(YY) of 'expiration' + CardExpYear => \( $year ), # YY from MM(-)YY(YY) of 'expiration' + CardStartMonth => \( $month ), # MM from MM(-)YY(YY) of 'card_start' + CardStartYear => \( $year ), # YY from MM(-)YY(YY) of 'card_start' + Track1 => 'track1', + Track2 => 'track2', + ACH + AccountNumber => 'account_number', + ABA => 'routing_code', + CheckNumber => 'check_number', + CardName => 'name', + DispositionType => 'DispositionType', + TotalAmount => 'amount' reformatted into cents + FeeAmount => 'FeeAmount', + TaxAmount => 'TaxAmount', + BillingAddress => 'address', + BillingCity => 'city', + BillingStateProv => 'state', + BillingPostalCode => 'zip', + BillingCountry => 'country', + BillingPhone => 'phone', + Email => 'email', + UserIPAddr => 'customer_ip', + UserHost => 'UserHost', + UDField1 => 'UDField1', + UDField2 => 'UDField2', + UDField3 => 'UDField3', + ActionCode => 'ActionCode', + IndustryInfo + Type => 'IndustryInfo', + ShippingInfo + CustomerPO => 'CustomerPO', + ShippingMethod => 'ShippingMethod', + ShippingName => 'ship_name', + ShippingAddr + Address => 'ship_address', + City => 'ship_city', + StateProv => 'ship_state', + Country => 'ship_country', + Phone => 'ship_phone', + +=head1 NOTE + +=head1 COMPATIBILITY + +Business::OnlinePayment::IPPay uses IPPay XML Product Specifications version +1.1.2. + +See http://www.ippay.com/ for more information. + +=head1 AUTHOR + +Jeff Finucane, ippay@weasellips.com + +=head1 SEE ALSO + +perl(1). L. + +=cut + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..c782755 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,10 @@ +IPPay.pm +Changes +MANIFEST +Makefile.PL +README +t/00load.t +t/card.t +t/check.t +t/bop.t +META.yml Module meta-data (added by MakeMaker) diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..bd2be95 --- /dev/null +++ b/META.yml @@ -0,0 +1,21 @@ +# http://module-build.sourceforge.net/META-spec.html +#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# +name: Business-OnlinePayment-IPPay +version: 0.01 +version_from: IPPay.pm +installdirs: site +requires: + Business::OnlinePayment: 3 + Crypt::SSLeay: 0 + Date::Calc: 0 + HTTP::Request: 0 + HTTP::Request::Common: 0 + LWP::UserAgent: 0 + Test::More: 0.42 + Text::CSV_XS: 0 + Tie::IxHash: 0 + XML::Simple: 0 + XML::Writer: 0 + +distribution_type: module +generated_by: ExtUtils::MakeMaker version 6.17 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..2fb1b8a --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,19 @@ +use ExtUtils::MakeMaker; +WriteMakefile( + 'NAME' => 'Business::OnlinePayment::IPPay', + 'VERSION_FROM' => 'IPPay.pm', # finds $VERSION + 'AUTHOR' => 'Jeff Finucane ', + 'PREREQ_PM' => { 'Business::OnlinePayment' => 3, +# 'Business::OnlinePayment::HTTPS' => 0, + 'Crypt::SSLeay' => 0, + 'Date::Calc' => 0, + 'HTTP::Request' => 0, + 'HTTP::Request::Common' => 0, + 'LWP::UserAgent' => 0, + 'Text::CSV_XS' => 0, + 'Test::More' => 0.42, + 'Tie::IxHash' => 0, + 'XML::Simple' => 0, + 'XML::Writer' => 0, + }, +); diff --git a/README b/README new file mode 100644 index 0000000..7054616 --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +Copyright (c) 1999 Jason Kohles. +Copyright (c) 2002-2003 Ivan Kohler +Copyright (c) 2008 Jeff Finucane +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::IPPay, a Business::OnlinePayment backend +module for IPPay. It is only useful if you have a merchant account with +IPPay Merchant Services: http://www.ippay.com + +This module implements the IPPay XML Product Specification Revision 1.1.2 + +Jeff Finucane is the original author. Please send patches as unified +diffs (diff -u). Mr. Kohles and Mr. Kohler provided material for cribbing. + +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"). diff --git a/t/00load.t b/t/00load.t new file mode 100644 index 0000000..e6664c4 --- /dev/null +++ b/t/00load.t @@ -0,0 +1,5 @@ +#!/usr/bin/perl -w + +use Test::More tests => 1; + +use_ok 'Business::OnlinePayment::IPPay'; diff --git a/t/bop.t b/t/bop.t new file mode 100644 index 0000000..68f5f98 --- /dev/null +++ b/t/bop.t @@ -0,0 +1,5 @@ +#!/usr/bin/perl -w + +use Test::More tests => 1; + +use_ok 'Business::OnlinePayment'; diff --git a/t/card.t b/t/card.t new file mode 100644 index 0000000..a0da538 --- /dev/null +++ b/t/card.t @@ -0,0 +1,184 @@ +#!/usr/bin/perl -w + +use Test::More; + +my($login, $password, @opts) = ('TESTMERCHANT', '', + 'default_Origin' => 'RECURRING' ); +plan tests => 43; + +use_ok 'Business::OnlinePayment'; + +my %content = ( + type => 'VISA', + login => $login, + password => $password, + action => 'Normal Authorization', + description => 'Business::OnlinePayment visa test', +# card_number => '4007000000027', + card_number => '4111111111111111', + cvv2 => '123', + expiration => expiration_date(), + amount => '49.95', + name => 'Tofu Beast', + email => 'ippay@weasellips.com', + address => '123 Anystreet', + city => 'Anywhere', + state => 'UT', + zip => '84058', + customer_id => 'tfb', +); + +my $voidable; +my $voidable_auth; +my $voidable_amount = 0; + +# valid card number test +{ + my $tx = Business::OnlinePayment->new("IPPay", @opts); + $tx->content(%content); + tx_check( + $tx, + desc => "valid card_number", + is_success => 1, + result_code => '000', + error_message => 'APPROVED', + authorization => qr/TEST\d{2}/, + avs_code => 'U', # so rather pointless :\ + cvv2_response => 'P', # ... + ); + $voidable = $tx->order_number if $tx->is_success; + $voidable_auth = $tx->authorization if $tx->is_success; + $voidable_amount = $content{amount} if $tx->is_success; +} + +# invalid card number test +{ + my $tx = Business::OnlinePayment->new("IPPay", @opts); + $tx->content(%content, card_number => "4111111111111112" ); + tx_check( + $tx, + desc => "invalid card_number", + is_success => 0, + result_code => '912', + error_message => 'INVALID CARD NUMBER', + authorization => qr/^$/, + avs_code => '', # so rather pointless :\ + cvv2_response => '', # ... + ); +} + +# authorization only test +{ + my $tx = Business::OnlinePayment->new("IPPay", @opts); + $tx->content(%content, action => 'authorization only', amount => '3.00' ); + tx_check( + $tx, + desc => "authorization only", + is_success => 1, + result_code => '000', + error_message => 'APPROVED', + authorization => qr/TEST\d{2}/, + avs_code => 'U', # so rather pointless :\ + cvv2_response => 'P', # ... + ); + $postable = $tx->order_number if $tx->is_success; + $postable_auth = $tx->authorization if $tx->is_success; + $postable_amount = $content{amount} if $tx->is_success; +} + +# post authorization test +SKIP: { + my $tx = new Business::OnlinePayment( "IPPay", %opts ); + $tx->content( %content, 'action' => "post authorization", + 'amount' => $postable_amount, # not required + 'order_number' => $postable, + ); + tx_check( + $tx, + desc => "post authorization", + is_success => 1, + result_code => '000', + error_message => 'APPROVED', + authorization => qr/^$postable_auth$/, + avs_code => '', + cvv2_response => '', + ); +} + +# void test +SKIP: { + my $tx = new Business::OnlinePayment( "IPPay", %opts ); + $tx->content( %content, 'action' => "Void", + 'order_number' => $voidable, + 'authorization' => $voidable_auth, + ); + tx_check( + $tx, + desc => "void", + is_success => 1, + result_code => '000', + error_message => 'VOID PROCESSED', + authorization => qr/^$voidable_auth$/, + avs_code => '', + cvv2_response => '', + ); +} + +# credit test +SKIP: { + my $tx = new Business::OnlinePayment( "IPPay", %opts ); + $tx->content( %content, 'action' => "credit"); + tx_check( + $tx, + desc => "credit", + is_success => 1, + result_code => '000', + error_message => 'RETURN ACCEPTED', + authorization => qr/\d{6}/, + avs_code => '', + cvv2_response => '', + ); +} + + +sub tx_check { + my $tx = shift; + my %o = @_; + + $tx->test_transaction(1); + $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->error_message, $o{error_message}, "error_message() / RESPMSG" ); + 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, qr/^\w{18}/, "order_number() / PNREF" ); +} + +sub tx_info { + my $tx = shift; + + no warnings 'uninitialized'; + + return ( + join( "", + "is_success(", $tx->is_success, ")", + " order_number(", $tx->order_number, ")", + " error_message(", $tx->error_message, ")", + " result_code(", $tx->result_code, ")", + " auth_info(", $tx->authorization, ")", + " avs_code(", $tx->avs_code, ")", + " cvv2_response(", $tx->cvv2_response, ")", + ) + ); +} + +sub expiration_date { + my($month, $year) = (localtime)[4,5]; + $year++; # So we expire next year. + $year %= 100; # y2k? What's that? + + return sprintf("%02d/%02d", $month, $year); +} diff --git a/t/check.t b/t/check.t new file mode 100644 index 0000000..7e05a52 --- /dev/null +++ b/t/check.t @@ -0,0 +1,99 @@ +#!/usr/bin/perl -w + +use Test::More; + +my($login, $password, %opt) = ('TESTMERCHANT', '', 'Origin' => 'RECURRING' ); +plan tests => 16; + +use_ok 'Business::OnlinePayment'; + +my %content = ( + type => 'CHECK', + login => $login, + password => $password, + action => 'Normal Authorization', + amount => '49.95', + customer_id => 'jsk', + name => 'Tofu Beast', + account_number => '12345', + routing_code => '111000025', # BoA in Texas taken from Wikipedia + bank_name => 'First National Test Bank', + account_type => 'Checking', +); + +my $voidable; + +#check test +{ + my $ctx = Business::OnlinePayment->new("IPPay", %opt); + $ctx->content(%content); + tx_check( + $ctx, + desc => 'normal ACH transaction', + is_success => 1, + result_code => '000', + error_message => 'CHECK ACCEPTED', + authorization => qr/^000000$/, + name => 'Tofu Beast', + ); + $voidable = $ctx->order_number if $ctx->is_success; +} + +#check void test +{ + my $ctx = Business::OnlinePayment->new("IPPay", %opt); + $ctx->content(%content, action => 'void', order_number => $voidable); + tx_check( + $ctx, + desc => 'ACH void transaction', + is_success => 1, + result_code => '000', + error_message => 'CHECK ACCEPTED', + authorization => qr/^000000$/, + ); +} + +#check credit test +{ + my $ctx = Business::OnlinePayment->new("IPPay", %opt); + $ctx->content(%content, action => 'credit'); + tx_check( + $ctx, + desc => 'ACH credit transaction', + is_success => 1, + result_code => '000', + error_message => 'CHECK ACCEPTED', + authorization => qr/^000000$/, + ); +} + +sub tx_check { + my $tx = shift; + my %o = @_; + + $tx->test_transaction(1); + $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->error_message, $o{error_message}, "error_message() / RESPMSG" ); + like( $tx->authorization, $o{authorization}, "authorization() / AUTHCODE" ); + like( $tx->order_number, qr/^\w{18}/, "order_number() / PNREF" ); +} + +sub tx_info { + my $tx = shift; + + no warnings 'uninitialized'; + + return ( + join( "", + "is_success(", $tx->is_success, ")", + " order_number(", $tx->order_number, ")", + " error_message(", $tx->error_message, ")", + " result_code(", $tx->result_code, ")", + " auth_info(", $tx->authorization, ")", + ) + ); +} + -- 2.11.0