From c93151a57800c9bd7a83f5dbbfe2cbd4861f8e68 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 24 Mar 2008 01:32:41 +0000 Subject: [PATCH 1/1] Initial import --- Changes | 5 + MANIFEST | 13 + META.yml | 21 ++ Makefile.PL | 19 ++ README | 22 ++ Vanco.pm | 643 ++++++++++++++++++++++++++++++++++++++++++ t/00load.t | 5 + t/bop.t | 5 + t/card_once.t | 32 +++ t/card_recurring.t | 52 ++++ t/check.t | 27 ++ t/lib/test_account.pl | 37 +++ t/test_account.eg | 6 + 13 files changed, 887 insertions(+) create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100644 Vanco.pm create mode 100644 t/00load.t create mode 100644 t/bop.t create mode 100644 t/card_once.t create mode 100644 t/card_recurring.t create mode 100644 t/check.t create mode 100644 t/lib/test_account.pl create mode 100644 t/test_account.eg diff --git a/Changes b/Changes new file mode 100644 index 0000000..846431c --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for Perl extension Business::OnlinePayment::Vanco. + +0.01 Fri Feb 29 22:34:17 2008 + - original version; + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..0ad9d8c --- /dev/null +++ b/MANIFEST @@ -0,0 +1,13 @@ +Vanco.pm +Changes +MANIFEST +Makefile.PL +README +t/00load.t +t/card_once.t +t/card_recurring.t +t/check.t +t/bop.t +t/test_account.eg +t/lib/test_account.pl +META.yml Module meta-data (added by MakeMaker) diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..4dc2f18 --- /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-Vanco +version: 0.01 +version_from: Vanco.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..14fca19 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,19 @@ +use ExtUtils::MakeMaker; +WriteMakefile( + 'NAME' => 'Business::OnlinePayment::Vanco', + 'VERSION_FROM' => 'Vanco.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..dde3093 --- /dev/null +++ b/README @@ -0,0 +1,22 @@ +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::Vanco, a Business::OnlinePayment backend +module for Vanco. It is only useful if you have a merchant account with +Vanco Services: http://www.vancoservices.com + +This module implements the Vanco Standeard Web Services XML API as of +February 29, 2008. + +For testing edit t/test_account, then run make test. + +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/Vanco.pm b/Vanco.pm new file mode 100644 index 0000000..290f97b --- /dev/null +++ b/Vanco.pm @@ -0,0 +1,643 @@ +package Business::OnlinePayment::Vanco; + +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); # Business::OnlinePayment::HTTPS +$VERSION = '0.01'; +$DEBUG = 1; +$me = 'Business::OnlinePayment::Vanco'; + +sub set_defaults { + my $self = shift; + my %opts = @_; + + # standard B::OP methods/data + $self->server('www.vancoservices.com') unless $self->server; + $self->port('443') unless $self->port; + $self->path('/cgi-bin/ws.vps') unless $self->path; + + $self->build_subs(qw( order_number avs_code cvv2_response + response_page response_code response_headers + )); + + # module specific data + foreach (qw( ClientID ProductID )) { + $self->build_subs($_); + + if ( $opts{$_} ) { + $self->$_( $opts{$_} ); + delete $opts{$_}; + } + } + +} + +sub map_fields { + my($self) = @_; + + my %content = $self->content(); + my $action = lc($content{'action'}); + + # ACTION MAP + my %actions = + ( 'normal authorization' => 'EFTAddCompleteTransaction', + 'recurring authorization' => 'EFTAddCompleteTransaction', + 'cancel recurring authorization' => 'EFTDeleteTransaction', + ); + $content{'RequestType'} = $actions{$action} || $action; + + # 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'}); + + # CHECK/TRANSACTION TYPE MAP + $content{'TransactionTypeCode'} = $content{'check_type'} || 'PPD' + unless ( $content{'TransactionTypeCode'} + || $content{'RequestType'} eq 'EFTDeleteTransaction'); # kludgy + + # let FrequencyCode, StartDate, and EndDate be specified directly; + unless($content{FrequencyCode}){ + my ($length,$unit) = + ($self->{_content}->{interval} or '') =~ + /^\s*(\d+)\s+(day|month)s?\s*$/; + + my %daily = ( '7' => 'W', + '14' => 'BW', + ); + + my %monthly = ( '1' => 'M', + '3' => 'Q', + '12' => 'A', + ); + + if ($length && $unit) { + $content{'FrequencyCode'} = $daily{$length} + if ($unit eq 'day'); + + $content{'FrequencyCode'} = $monthly{$length} + if ($unit eq 'month'); + } + } + + unless($content{StartDate}){ + $content{'StartDate'} = $content{'start'}; + } + + unless($content{EndDate}){ + my ($year,$month,$day) = + $content{StartDate} =~ /^\s*(\d{4})-(\d{1,2})-(\d{1,2})\s*$/ + if $content{StartDate}; + + my ($periods) = $content{periods} =~/^\s*(\d+)\s*$/ + if $content{periods}; + + my %daily = ( 'W' => '7', + 'BW' => '14', + ); + + my %monthly = ( 'M' => '1', + 'Q' => '3', + 'A' => '12', + ); + + if ($year && $month && $day && $periods) { + if ($daily{$content{FrequencyCode}}) { + my $days = ($periods - 1) * $daily{$content{FrequencyCode}}; + ($year, $month, $day) = Add_Delta_Days( $year, $month, $day, $days); + $content{EndDate} = sprintf("%04d-%02d-%02d", $year, $month, $day); + } + + if ($monthly{$content{FrequencyCode}}) { + my $months = ($periods - 1) * $monthly{$content{FrequencyCode}}; + ($year, $month, $day) = Add_Delta_YM( $year, $month, $day, 0, $months); + $content{EndDate} = sprintf("%04d-%02d-%02d", $year, $month, $day); + } + } + + } + + if ($action eq 'normal authorization'){ + my $time = time + 86400 if $self->transaction_type() eq 'ECHECK'; + $content{'FrequencyCode'} = 'O'; + $content{'StartDate'} = $content{'start'} || substr(today($time),0,10); + $content{'EndDate'} = $content{'StartDate'}; + } + + + # ACCOUNT TYPE MAP + my %account_types = ('personal checking' => 'C', + 'personal savings' => 'S', + 'business checking' => 'C', + 'business savings' => 'S', + 'checking' => 'C', + 'savings' => 'S', + ); + $content{'account_type'} = $account_types{lc($content{'account_type'})} + || $content{'account_type'}; + $content{'account_type'} = 'CC' if lc($content{'type'}) eq 'cc'; + + # SHIPPING INFORMATION + foreach (qw(name address city state zip)) { + $content{"ship_$_"} = $content{$_} unless $content{"ship$_"}; + } + + # 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 today { + my @time = localtime($_[0] ? shift : time); + $time[5] += 1900; + $time[4]++; + sprintf("%04d-%02d-%02d %02d:%02d:%02d", reverse(@time[0..5])); +} + +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); + unless($self->ClientID() && $self->ProductID()) { + croak "ClientID and ProductID are required"; + } + + my $requestid = time . sprintf("%010u", rand() * 2**32); + my $auth_requestid = $requestid . '0'; + my $req_requestid = $requestid . '1'; + + $self->map_fields(); + + my @required_fields = qw(action login password); + + if ( lc($self->{_content}->{action}) eq 'normal authorization' ) { + push @required_fields, qw( type amount name ); + + push @required_fields, qw( card_number expiration ) + if ($self->transaction_type() eq "CC"); + + push @required_fields, + qw( routing_code account_number account_type ) + if ($self->transaction_type() eq "ECHECK"); + + }elsif ( lc($self->{_content}->{action}) eq 'recurring authorization' ) { + push @required_fields, qw( type interval start periods amount name ); + + push @required_fields, qw( card_number expiration ) + if ($self->transaction_type() eq 'CC' ); + + push @required_fields, + qw( routing_code account_number account_type ) + if ($self->transaction_type() eq "ECHECK"); + + }elsif ( lc($self->{_content}->{action}) eq 'cancel recurring authorization' ) { + push @required_fields, qw( subscription ); + + }else{ + croak "$me can't handle transaction type: ". + $self->{_content}->{action}. " for ". + $self->transaction_type(); + } + + $self->required_fields(@required_fields); + + tie my %auth, 'Tie::IxHash', ( + RequestType => 'Login', + RequestID => $auth_requestid, + RequestTime => today(), + ); + + tie my %requestvars, 'Tie::IxHash', + $self->revmap_fields( + UserID => 'login', + Password => 'password', + ); + $requestvars{'ProductID'} = $self->ProductID(); + + tie my %req, 'Tie::IxHash', + $self->revmap_fields ( + Auth => \%auth, + Request => { RequestVars => \%requestvars }, + ); + + my $response = $self->_my_https_post(%req); + return if $self->result_code(); + + tie %auth, 'Tie::IxHash', + $self->revmap_fields( RequestType => 'RequestType'); + $auth{'RequestID'} = $req_requestid; + $auth{'RequestTime'} = today(); + $auth{'SessionID'} = $response->{Response}->{SessionID}; + + my $client_id = $self->ClientID(); + my $cardexpmonth = $self->expdate_month($self->{_content}->{expiration}); + my $cardexpyear = $self->expdate_year($self->{_content}->{expiration}); + my $account_number = ( defined($self->transaction_type()) + && $self->transaction_type() eq 'CC') + ? $self->{_content}->{card_number} + : $self->{_content}->{account_number} + ; + + tie %requestvars, 'Tie::IxHash', + $self->revmap_fields( + ClientID => \$client_id, + CustomerID => 'customer_id', + CustomerName => 'ship_name', # defaults to + CustomerAddress1 => 'ship_address',# values without + CustomerCity => 'ship_city', # ship_ prefix + CustomerState => 'ship_state', # + CustomerZip => 'ship_zip', # + CustomerPhone => 'phone', + AccountType => 'account_type', + AccountNumber => \$account_number, + RoutingNumber => 'routing_code', + CardBillingName => 'name', + CardExpMonth => \$cardexpmonth, + CardExpYear => \$cardexpyear, + CardCVV2 => 'cvv2', + CardBillingAddr1 => 'address', + CardBillingCity => 'city', + CardBillingState => 'state', + CardBillingZip => 'zip', + Amount => 'amount', + StartDate => 'StartDate', + EndDate => 'EndDate', + FrequencyCode => 'FrequencyCode', + TransactionTypeCode => 'TransactionTypeCode', + TransactionRef => 'subscription', + ); + + tie %req, 'Tie::IxHash', + $self->revmap_fields ( + Auth => \%auth, + Request => { RequestVars => \%requestvars }, + ); + + $response = $self->_my_https_post(%req); + $self->order_number($response->{Response}->{TransactionRef}); + + $self->is_success(1); + if ($self->result_code()) { + $self->is_success(0); + unless ( $self->error_message() ) { #additional logging information + my %headers = %{$self->response_headers()}; + $self->error_message( + "(HTTPS response: ". $self->result_code(). ") ". + "(HTTPS headers: ". + join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ". + "(Raw HTTPS content: ". $self->server_response(). ")" + ); + } + } + +} + +sub _my_https_post { + my $self = shift; + my %req = @_; + my $post_data; + my $writer = new XML::Writer( OUTPUT => \$post_data, + DATA_MODE => 1, + DATA_INDENT => 1, +# ENCODING => 'us-ascii', + ); + $writer->xmlDecl(); + $writer->startTag('VancoWS'); + foreach ( keys ( %req ) ) { + $self->_xmlwrite($writer, $_, $req{$_}); + } + $writer->endTag('VancoWS'); + $writer->end(); + + if ($self->test_transaction()) { + $self->server('www.vancodev.com'); + $self->port('443'); + $self->path('/cgi-bin/wstest.vps'); + } + + my $url = "https://" . $self->server. ':'; + $url .= $self->port || '443'; + $url .= $self->path; + + my $ua = new LWP::UserAgent; + my $res = $ua->request( POST( $url, 'Content_Type' => 'form-data', + 'Content' => [ 'xml' => $post_data ]) + ); + + warn $post_data if $DEBUG; + my($page,$server_response,%headers) = ( + $res->content, + $res->code. ' ' . $res->message, + map { $_ => $res->header($_) } $res->header_field_names + ); + + warn $page if $DEBUG; + + my $response; + my $error; + if ($server_response =~ /200/){ + $response = XMLin($page); + if ( exists($response->{Response}) + && !exists($response->{Response}->{Errors})) { # so much for docs + $error->{ErrorDescription} = ''; + $error->{ErrorCode} = ''; + }elsif (ref($response->{Response}->{Errors}) eq 'ARRAY') { + $error = $response->{Response}->{Errors}->[0]; + }else{ + $error = $response->{Response}->{Errors}->{Error}; + } + }else{ + $error->{ErrorDescription} = "Server Failed"; + $error->{ErrorCode} = $server_response; + } + + $self->result_code($error->{ErrorCode}); + $self->error_message($error->{ErrorDescription}); + + $self->server_response($page); + $self->response_page($page); + $self->response_headers(\%headers); + return $response; +} + +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::Vanco - Vanco Services backend for Business::OnlinePayment + +=head1 SYNOPSIS + + use Business::OnlinePayment; + + #### + # One step transaction, the simple case. + #### + + my $tx = new Business::OnlinePayment( "Vanco", + ClientID => 'CL1234', + ProductID => 'EFT', + ); + $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"; + } + + #### + # One step subscription, the simple case. + #### + + my $tx = new Business::OnlinePayment( "Vanco", + ClientID => 'CL1234', + ProductID => 'EFT', + ); + $tx->content( + type => 'CC', + login => 'testdrive', + password => 'testpass', + action => 'Recurring Authorization', + interval => '7 days', + start => '2008-3-10', + periods => '16', + amount => '99.95', + description => 'Business::OnlinePayment test', + customer_id => 'vip', + name => 'Tofu Beast', + address => '123 Anystreet', + city => 'Anywhere', + state => 'GA', + zip => '84058', + card_number => '4111111111111111', + expiration => '09/02', + ); + $tx->submit(); + + if($tx->is_success()) { + print "Card processed successfully: ".$tx->order_number."\n"; + } else { + print "Card was rejected: ".$tx->error_message."\n"; + } + my $subscription = $tx->order_number + + + #### + # Subscription cancellation. It happens. + #### + + $tx->content( + subscription => '99W2D', + login => 'testdrive', + password => 'testpass', + action => 'Cancel Recurring Authorization', + ); + $tx->submit(); + + if($tx->is_success()) { + print "Cancellation processed successfully."\n"; + } else { + print "Cancellation was rejected: ".$tx->error_message."\n"; + } + + +=head1 SUPPORTED TRANSACTION TYPES + +=head2 CC, Visa, MasterCard, American Express, Discover + +Content required: type, login, password, action, amount, name, card_number, expiration. + +=head2 Check + +Content required: type, login, password, action, amount, name, account_number, routing_code, account_type. + +=head2 Subscriptions + +Additional content required: interval, start, periods. + +=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 + recurring authorization + cancel recurring authorization + +=head2 interval + + Interval contains a number of digits, whitespace, and the units of days or months in either singular or plural form. + +=head1 Setting Vanco parameters from content(%content) + +The following rules are applied to map data to AuthorizeNet ARB parameters +from content(%content): + + # param => $content{} + Auth + UserId => 'login', + Password => 'password', + Request + RequestVars + CustomerID => 'customer_id', + CustomerName => 'ship_name', + CustomerAddress1 => 'ship_address', + CustomerCity => 'ship_city', + CustomerState => 'ship_state', + CustomerZip => 'ship_zip', + CustomerPhone => 'phone', + AccountType => 'account_type', # C, S, or CC + AccountNumber => 'account_number' # or card_number + RoutingNumber => 'routing_code', + CardBillingName => 'name', + CardExpMonth => \( $month ), # YYYY-MM from 'expiration' + CardExpYear => \( $year ), # YYYY-MM from 'expiration' + CardCVV2 => 'cvv2', + CardBillingAddr1 => 'address', + CardBillingCity => 'city', + CardBillingState => 'state', + CardBillingZip => 'zip', + Amount => 'amount', + StartDate => 'start', + EndDate => calculated_from start, periods, interval, + FrequencyCode => [O,M,W,BW,Q, or A determined from interval], + TransactionTypeCode => 'check_type', # (or PPD by default) + +=head1 NOTE + +To cancel a recurring authorization transaction, submit the TransactionRef +in the field "subscription" with the action set to "Cancel Recurring +Authorization". You can get the TransactionRef from the authorization by +calling the order_number method on the object returned from the authorization. + +=head1 COMPATIBILITY + +Business::OnlinePayment::Vanco uses Vanco Services' "Standard Web Services +XML API" as described on February 29, 2008. The describing documents +are protected by a non-disclosure agreement. + +See http://www.vancoservices.com/ for more information. + +=head1 AUTHOR + +Jeff Finucane, vanco@weasellips.com + +=head1 SEE ALSO + +perl(1). L. + +=cut + diff --git a/t/00load.t b/t/00load.t new file mode 100644 index 0000000..13577fc --- /dev/null +++ b/t/00load.t @@ -0,0 +1,5 @@ +#!/usr/bin/perl -w + +use Test::More tests => 1; + +use_ok 'Business::OnlinePayment::Vanco'; 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_once.t b/t/card_once.t new file mode 100644 index 0000000..4e9f21e --- /dev/null +++ b/t/card_once.t @@ -0,0 +1,32 @@ +#!/usr/bin/perl -w + +use Test::More; +require "t/lib/test_account.pl"; + +my($login, $password, @opts) = test_account_or_skip(); +plan tests => 2; + +use_ok 'Business::OnlinePayment'; + +my $tx = Business::OnlinePayment->new("Vanco", @opts); +$tx->content( + type => 'VISA', + login => $login, + password => $password, + action => 'Normal Authorization', + description => 'Business::OnlinePayment visa test', + amount => '49.95', + customer_id => 'tfb', + name => 'Tofu Beast', + address => '123 Anystreet', + city => 'Anywhere', + state => 'UT', + zip => '84058', +# card_number => '4007000000027', + card_number => '4111111111111111', + expiration => expiration_date(), +); +$tx->test_transaction(1); # test, dont really charge +$tx->submit(); + +ok($tx->is_success()) or diag $tx->error_message; diff --git a/t/card_recurring.t b/t/card_recurring.t new file mode 100644 index 0000000..b7dbfd6 --- /dev/null +++ b/t/card_recurring.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl -w + +use Test::More; +require "t/lib/test_account.pl"; + +my($login, $password, %opts) = test_account_or_skip(); +plan tests => 4; + +use_ok 'Business::OnlinePayment'; + +my $tx = Business::OnlinePayment->new("Vanco", %opts); +$tx->content( + type => 'VISA', + login => $login, + password => $password, + action => 'Recurring Authorization', + description => 'Business::OnlinePayment visa test', + amount => '49.95', + customer_id => 'tofu', + name => 'Tofu Beast', + address => '123 Anystreet', + city => 'Anywhere', + state => 'UT', + zip => '84058', + card_number => '5105105105105100', + expiration => expiration_date(), + interval => '1 month', + start => tomorrow(), + periods => '3', +); +$tx->test_transaction(1); # test, dont really charge +$tx->submit(); + +ok($tx->is_success()) or diag $tx->error_message; + +my $subscription = $tx->order_number(); +like($subscription, qr/^[0-9]{1,13}$/, "Get order number"); + +SKIP: { + + skip "No order number", 1 unless $subscription; + + $tx->content( + login => $login, + password => $password, + action => 'Cancel Recurring Authorization', + subscription => $subscription, + ); + $tx->test_transaction(1); + $tx->submit(); + ok($tx->is_success()) or diag $tx->error_message; +} diff --git a/t/check.t b/t/check.t new file mode 100644 index 0000000..6d5b7c8 --- /dev/null +++ b/t/check.t @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w + +use Test::More; +require "t/lib/test_account.pl"; + +my($login, $password, %opt) = test_account_or_skip(); +plan tests => 2; + +use_ok 'Business::OnlinePayment'; + +my $ctx = Business::OnlinePayment->new("Vanco", %opt); +$ctx->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', +); +$ctx->test_transaction(1); # test, dont really charge +$ctx->submit(); +ok( $ctx->is_success() ) || diag $ctx->error_message; diff --git a/t/lib/test_account.pl b/t/lib/test_account.pl new file mode 100644 index 0000000..9ced583 --- /dev/null +++ b/t/lib/test_account.pl @@ -0,0 +1,37 @@ +sub test_account_or_skip { + my $suffix = shift; + my($login, $password, @opts) = test_account($suffix); + + unless( defined $login ) { + plan skip_all => "No test account"; + } + + return($login, $password, @opts); +} + +sub test_account { + my $suffix = shift || ''; + $suffix = "_$suffix" if $suffix; + open TEST_ACCOUNT, "t/test_account$suffix" or return; + my($login, $password, @opts) = ; + chomp $login; + chomp $password; + chomp foreach @opts; + + return($login, $password, @opts); +} + +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); +} + +sub tomorrow { + my($day, $month, $year) = (localtime(time+86400))[3..5]; + return sprintf("%04d-%02d-%02d", $year+1900, ++$month, $day); +} + +1; diff --git a/t/test_account.eg b/t/test_account.eg new file mode 100644 index 0000000..cf9ea21 --- /dev/null +++ b/t/test_account.eg @@ -0,0 +1,6 @@ +the user id goes here +the password goes here +ClientID +the client id goes here +ProductID +EFT -- 2.20.1