From: Alex Brelsfoard Date: Mon, 9 Feb 2015 16:58:10 +0000 (-0500) Subject: Getting code ready for CPAN and debian relase. X-Git-Url: http://git.freeside.biz/gitweb/?a=commitdiff_plain;h=1e90419f5b23ab4b4fe17b5861958d75ac285c4d;p=Business-OnlinePayment-vSecureProcessing.git Getting code ready for CPAN and debian relase. --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9788afa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +blib/ +*.sw? +Makefile +Makefile.old +MYMETA.yml +pm_to_blib diff --git a/Changes b/Changes new file mode 100644 index 0000000..716346c --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for Perl module Business::OnlinePayment::vSecureProcessing + +0.01 Feb 06 2015 + Initial release. + diff --git a/MANIFEST b/MANIFEST index 2c03f96..df6a903 100644 --- a/MANIFEST +++ b/MANIFEST @@ -4,10 +4,10 @@ Makefile.PL README ignore.txt lib/Business/OnlinePayment/vSecureProcessing.pm -t/00-load.t -t/boilerplate.t -t/manifest.t -t/pod-coverage.t -t/pod.t -t/transaction.t -t/transaction_decline.t +t/t_00-load.t +t/t_boilerplate.t +t/t_manifest.t +t/t_pod-coverage.t +t/t_pod.t +t/t_transaction.t +t/t_transaction_decline.t diff --git a/extra/test.pl b/extra/test.pl new file mode 100755 index 0000000..bca3628 --- /dev/null +++ b/extra/test.pl @@ -0,0 +1,96 @@ +#!/usr/bin/perl -w + +# +# Make sure to copy Business::OnlinePayment::vSecureProcessing into its +# proper system directory (aka /usr/share/perl5/Business/Onlinepayment) +# + +use strict; +use Data::Dumper; +use Business::OnlinePayment; + +my %opt = ( + server =>'dvrotsos2.kattare.com', + platform => 'Buypass', + gid => '1432479912596791', + tid => '01', + userid=> 'tom@yiptv.com', + port => 443, + env => 'test' +); + +my $action = shift || 'Normal Authorization'; +my $auth = shift || ''; + + +my %content = ( + appid => 'yiptv', + action => $action, + auth => $auth, + description => 'Business::OnlinePayment visa test', +# card_number => '4007000000027', + card_number => '4111111111111111', + cvv2 => '111', + expiration => expiration_date(), + amount => '42.24', + name => 'Murphy Law', + email => 'fake@acme.com', + address => '123 Anystreet', + zip => '84058', +); + +main(); + +sub main { + my $transaction = Business::OnlinePayment->new("vSecureProcessing", %opt); + + print "MAKING PAYMENT\n"; + ProcessTransaction($transaction); + $content{'action'} = 'void'; + $content{'ref_num'} = $transaction->authorization(); + $content{'txn_date'} = $transaction->txn_date(); + $content{'amount'} = $transaction->txn_amount; + print "VOIDING PAYMENT\n"; + ProcessTransaction($transaction); + $content{'action'} = 'Normal Authorization'; + $content{'amount'} = '30.00'; + print "MAKING PAYMENT\n"; + ProcessTransaction($transaction); + $content{'action'} = 'credit'; + $content{'ref_num'} = $transaction->authorization; + $content{'txn_date'} = $transaction->txn_date; + $content{'amount'} = $transaction->txn_amount; + print "REFUNDING PAYMENT\n"; + ProcessTransaction($transaction); +} + +sub ProcessTransaction { + my $transaction = shift; + print "Processing transaction with content:\n".Dumper(\%content)."\n"; + $transaction->content(%content); + + eval { $transaction->submit(); }; + + if ( $@ ) { + + die "Error: $@\n"; + + } else { + + if ( $transaction->is_success() ) { + print "Card processed successfully: ". $transaction->authorization()."\n"; + } else { + print "Card was rejected: ". $transaction->error_message(). "\n"; + } + } +} + + +sub expiration_date { + my($month, $year) = (localtime)[4,5]; + $month += 1; + $year++; # So we expire next year. + $year %= 100; # y2k? What's that? + + return sprintf("%02d/%02d", $month, $year); +} diff --git a/ignore.txt b/ignore.txt new file mode 100644 index 0000000..997590b --- /dev/null +++ b/ignore.txt @@ -0,0 +1,12 @@ +blib* +Makefile +Makefile.old +Build +Build.bat +_build* +pm_to_blib* +*.tar.gz +.lwpcookies +cover_db +pod2htm*.tmp +Business-OnlinePayment-vSecureProcessing-* \ No newline at end of file diff --git a/lib/Business/OnlinePayment/vSecureProcessing.pm b/lib/Business/OnlinePayment/vSecureProcessing.pm new file mode 100644 index 0000000..e7514a3 --- /dev/null +++ b/lib/Business/OnlinePayment/vSecureProcessing.pm @@ -0,0 +1,690 @@ +package Business::OnlinePayment::vSecureProcessing; + +use strict; +use Carp; +use Template; +use XML::Simple; +use Data::Dumper; + +use Business::OnlinePayment; +use Business::OnlinePayment::HTTPS; +#use Net::SSLeay qw(post_http post_https make_headers make_form); +use vars qw($VERSION $DEBUG @ISA $me); + +@ISA = qw(Business::OnlinePayment::HTTPS); +$DEBUG = 0; +$VERSION = '0.01'; +$me = 'Business::OnlinePayment::vSecureProcessing'; + + +# $server: http://dvrotsos2.kattare.com + +# mapping out all possible endpoints +# but this version will only be building out "charge", "void", & "credit" +my %payment_actions = ( + 'charge' => { + path => '/vsg2/processpayment', + }, + 'void' => { + path => '/vsg2/processvoid', + }, + 'refund' => { + path => '/vsg2/processrefund', + }, + 'authorize' => { + path => '/vsg2/processauth', + }, + 'authorize_cancel' => { + path => '/vsg2/processauthcancel', + }, + 'capture' => { + path => '/vsg2/processcaptureonly', + }, + 'create_token' => { + path => '/vsg2/createtoken', + }, + 'delete_token' => { + path => '/vsg2/deletetoken', + }, + 'query_token' => { + path => '/vsg2/querytoken', + }, + 'update_exp_date' => { + path => '/vsg2/updateexpiration', + }, + 'update_token' => { + path => '/vsg2/updatetoken', + }, + +); + +my %action_mapping = ( + 'normal authorization' => 'charge', + 'credit' => 'refund', + 'authorization only' => 'authorize', + 'post authorization' => 'capture', + 'reverse authorization' => 'authorize_cancel' + # void => void +); + +sub set_defaults { + my $self = shift; + my %options = @_; + + # inistialize standard B::OP attributes + $self->is_success(0); + $self->$_( '' ) for qw/authorization + result_code + error_message + server + port + path + server_response/; + + # B::OP creates the following accessors: + # server, port, path, test_transaction, transaction_type, + # server_response, is_success, authorization, + # result_code, error_message, + + $self->build_subs(qw/ + env platform userid gid tid appid action reference_number cvv_response + avs_response risk_score txn_amount txn_date response_code + /); + + $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG; + + + + $self->server($options{'server'}); + + $self->gid($options{'gid'}); + + $self->tid($options{'tid'}); + + $self->platform($options{'platform'}); + + $self->appid($options{'appid'}); + + $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test' + + $self->port(443); +} + + + +sub clean_content { + my ($self,$content) = @_; + my %content = $self->content(); + + { + no warnings 'uninitialized'; + + # strip non-digits from card number + my $card_number = ''; + if ( $content{card_number} ) { + $content{card_number} =~ s/\D//g; + } + + # separate month and year values for expiry_date + if ( $content{expiration} ) { + ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration}; + $content{exp_month} = sprintf "%02d", $content{exp_month}; + $content{exp_year} = substr($content{exp_year},0,2) if ($content{exp_year} > 99); + } + + if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) { + ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2); + } + + if ($content{'address'} =~ m/[\D ]*(\d+)\D/) { + $content{'street_number'} = $1; + } + } + warn "Content after cleaning:\n".Dumper(\%content)."\n" if ($DEBUG >2); + $self->content(%content); +} + +sub process_content { + my $self = shift; + $self->clean_content(); + my %content = $self->content(); + $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'}); + $self->path($payment_actions{ $self->action }{path}) + unless length($self->path); + $self->appid($content{appid}) if (!$self->appid && $content{appid}); +} + +sub submit { + my $self = shift; + + # inistialize standard B::OP attributes + $self->is_success(0); + $self->$_( '' ) for qw/authorization + result_code + error_message + server_response/; + + # clean and process the $self->content info + $self->process_content(); + my %content = $self->content; + my $action = $self->action(); + + my @acceptable_actions = ('charge', 'refund', 'void'); + + unless ( grep { $action eq $_ } @acceptable_actions ) { + croak "'$action' is not supported at this time."; + } + + # fill out the template vars + my $template_vars = { + + auth => { + platform => $self->platform, + userid => $self->userid, + gid => $self->gid, + tid => $self->tid + }, + + payment => { + amount => $content{'amount'}, + track1 => ($content{'track1'}) ? $content{'track1'} : '', + track2 => ($content{'track2'}) ? $content{'track2'} : '', + type => ($content{'description'}) ? $content{'description'} : '', + cf1 => ($content{'UDField1'}) ? $content{'UDField1'} : '', + cf2 => ($content{'UDField2'}) ? $content{'UDField2'} : '', + cf3 => '', + account_number => ($content{'card_number'}) ? $content{'card_number'} : '', + exp_month => $content{'exp_month'}, + exp_year => $content{'exp_year'}, + cvv => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '', + first_name => ($content{'first_name'}) ? $content{'first_name'} : '', + last_name => ($content{'last_name'}) ? $content{'last_name'} : '', + postal_code => ($content{'zip'}) ? $content{'zip'} : '', + street_address => ($content{'street_number'}) ? $content{'street_number'} : '', + industry_type => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '', + invoice_num => ($content{'invoice_number'}) ? $content{'invoice_number'} : '', + appid => $self->appid(), + recurring => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0, + response_code => ($content{'response_code'}) ? $content{'response_code'} : '', + reference_number=> ($content{'ref_num'}) ? $content{'ref_num'} : '', + token => ($content{'token'}) ? $content{'token'} : '', + receipt => ($content{'receipt'}) ? $content{'receipt'} : '', + transaction_date=> ($content{'txn_date'}) ? $content{'txn_date'} : '', + merchant_data => ($content{'merchant_data'}) ? $content{'merchant_data'} : '', + }, + + # we won't be using level2 nor level3. So I'm leaving them blank for now. + level2 => { + card_type => '', + purchase_code => '', + country_code => '', + ship_tp_postal_code => '', + ship_from_postal_code => '', + sales_tax => '', + product_description1 => '', + product_description2 => '', + product_description3 => '', + product_description4 => '' + }, + + level3 => { + purchase_order_num => '', + order_date => '', + duty_amount => '', + alt_tax_amount => '', + discount_amount => '', + freight_amount => '', + tax_exempt => '', + line_item_count => '', + purchase_items => $self->_parse_line_items() + } + }; + + + # create the list of required fields based on the action + my @required_fields = qw/ amount /; + if ($action eq 'charge') { + push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /); + }elsif ($action eq 'void') { + push(@required_fields, $_) foreach (qw/ reference_number transaction_date /); + }elsif ($action eq 'refund') { + push(@required_fields, $_) foreach (qw/ amount account_number exp_month exp_year /); + } + + # check the requirements are met. + my @missing_fields; + foreach my $field (@required_fields) { + push(@missing_fields, $field) if (!$template_vars->{payment}{$field}); + } + if (scalar(@missing_fields)) { + croak "Missing required fields: ".join(', ', @missing_fields); + } + + # read in the appropriate xml template + my $xml_template = _get_xml_template( $action ); + # create a template object. + my $tt = Template->new(); + # populate the XML template. + my $xml_data; + $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error(); + + warn "XML:\n$xml_data\n" if $DEBUG > 2; + + my $boundary = sprintf('FormBoundary%06d', int(rand(1000000))); + # opts for B:OP:HTTPS::https_post + my $opts = { headers => {}}; + $opts->{'Content-Type'} = + $opts->{headers}->{'Content-Type'} = + "multipart/form-data, boundary=$boundary"; + + my $content = + "--$boundary\n". + "Content-Disposition: form-data; name=\"param\"\n\n". + $xml_data."\n". + "--$boundary--\n"; + + # conform to RFC standards + $content =~ s/\n/\r\n/gs; + + my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content ); + + # store the server response. + $self->server_response($server_response); + # parse the result page. + $self->parse_response($page); + + if (!$self->is_success() && !$self->error_message() ) { + if ( $DEBUG ) { + #additional logging information, possibly too sensitive for an error msg + # (vSecureProcessing seems to have a failure mode where they return the full + # original request including card number) + $self->error_message( + "(HTTPS response: ".$server_response.") ". + "(HTTPS headers: ". + join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ". + "(Raw HTTPS content: ".$page.")" + ); + } else { + my $response_code = $self->response_code() || ''; + if ($response_code) { + $self->error_message(qq|Error code ${response_code} was returned by vSecureProcessing. (enable debugging for raw HTTPS response)|); + }else { + $self->error_message('No error information was returned by vSecureProcessing (enable debugging for raw HTTPS response)'); + } + } + } + +} + +# read $self->server_response and decipher any errors +sub parse_response { + my $self = shift; + my $page = shift; + + if ($self->server_response =~ /^200/) { + my $response = XMLin($page); + $self->result_code($response->{Status}); # 0 /1 + $self->response_code($response->{ResponseCode}); # see documentation for translation + $self->avs_response($response->{AvsResponse}); # Y / N + $self->cvv_response($response->{CvvResponse}); # P / F + $self->txn_date($response->{TransactionDate}); # MMDDhhmmss + $self->txn_amount($response->{TransactionAmount} / 100); # 00000003500 / 100 + $self->reference_number($response->{ReferenceNumber}); + + $self->is_success($self->result_code() eq '0' ? 1 : 0); + if ($self->is_success()) { + $self->authorization($response->{AuthIdentificationResponse}); + } + # fill in error_message if there is is an error + if ( !$self->is_success && exists($response->{AdditionalResponseData})) { + $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{AdditionalResponseData}); + }elsif ( !$self->is_success && exists($response->{Receipt}) ) { + $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : ''); + } + + }else { + $self->is_success(0); + $self->error_message('Error communicating with vSecureProcessing server'); + return; + } + + +} + +sub _get_xml_template { + my $action = shift; + + my $xml_template = q| + + [% auth.platform %] + [% auth.userid %] + [% auth.gid %] + [% auth.tid %] + + |; + + if ($action eq 'charge') { + $xml_template .= _get_xml_template_charge(); + }elsif($action eq 'void') { + $xml_template .= _get_xml_template_void(); + }elsif($action eq 'authorize') { + $xml_template .= _get_xml_template_auth(); + }elsif($action eq 'authorize_cancel') { + $xml_template .= _get_xml_template_auth_cancel(); + }elsif($action eq 'refund') { + $xml_template .= _get_xml_template_refund(); + }elsif($action eq 'capture') { + $xml_template .= _get_xml_template_capture(); + }elsif($action eq 'create_token') { + $xml_template .= _get_xml_template_create_token(); + }elsif($action eq 'delete_token') { + $xml_template .= _get_xml_template_delete_token(); + }elsif($action eq 'query_token') { + $xml_template .= _get_xml_template_query_token(); + }elsif($action eq 'update_exp_date') { + $xml_template .= _get_xml_template_update_exp_date(); + }elsif($action eq 'update_token') { + $xml_template .= _get_xml_template_update_token(); + } + + $xml_template .= ""; + $xml_template =~ s/[\n\t\s]*//g; + + return $xml_template; +} + +sub _get_xml_template_charge { + my $xml_template = q| + [% payment.amount %] + [% payment.track1 %] + [% payment.track2 %] + [% payment.type %] + [% payment.cf1 %] + [% payment.cf2 %] + [% payment.cf3 %] + [% payment.account_number %] + [% payment.exp_month %] + [% payment.exp_year %] + [% payment.cvv %] + [% payment.first_name %] + [% payment.last_name %] + [% payment.postal_code %] + [% payment.street_address %] + + [% payment.industry_type %] + [% payment.invoice_num %] + + [% payment.appid %] + [% payment.recurring %] + |; + + # other options (that we are not using right now): +# +# [% level2.card_type %] +# [% level2.purchase_code %] +# [% level2.country_code %] +# [% level2.ship_tp_postal_code %] +# [% level2.ship_from_postal_code %] +# [% level2.sales_tax %] +# [% level2.product_description1 %] +# [% level2.product_description2 %] +# [% level2.product_description3 %] +# [% level2.product_description4 %] +# +# +# [% level3.purchase_order_num %] +# [% level3.order_date %] +# [% level3.duty_amount %] +# [% level3.alt_tax_amount %] +# [% level3.discount_amount %] +# [% level3.freight_amount %] +# [% level3.tax_exempt %] +# [% level3.line_item_count %] +# +# [% level3.purchase_items %] +# +# + + return $xml_template; +} + +sub _parse_line_items { + my $self = shift; + my %content = $self->content(); + + return '' if (!$content{'items'}); + + my @line_items; + my $template = q| + [% seq_num %] + [% code %] + [% desc %] + [% qty %] + [% unit %] + [% unit_cost %] + [% amount %] + [% discount_amount %] + [% tax_amount %] + [% tax_rate %] + |; + + + my @items = $content{'items'}; + foreach my $item (@items) { + # fille in the slots from $template with details in $item + # push to @line_items + } + + return join("\n", @line_items); +} + +sub _get_xml_template_void { + my $xml_template = q| + [% payment.amount %] + [% payment.account_number %] + [% payment.exp_month %] + [% payment.exp_year %] + [% payment.reference_number %] + + [% payment.industry_type %] + [% payment.appid %] + |; + + return $xml_template; +} + +sub _get_xml_template_refund { + my $xml_template = q| + [% payment.amount %] + [% payment.account_number %] + [% payment.exp_month %] + [% payment.exp_year %] + [% payment.appid %] + |; + + return $xml_template; +} + +sub _get_xml_template_auth { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_auth_cancel { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_capture { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_create_token { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_delete_token { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_query_token { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_update_exp_date { + my $xml_template = ''; + + return $xml_template; +} + +sub _get_xml_template_update_token { + my $xml_template = ''; + + return $xml_template; +} + + +1; +__END__ + + +=head1 NAME + +Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Business::OnlinePayment + +=head1 SYNOPSIS + + use Business::OnlinePayment; + my %processor_info = ( + platform => '####', + gid => 12345678901234567890, + tid => 01, + user_id => '####', + url => 'www.####.com' + ); + my $tx = + new Business::OnlinePayment( "vSecureProcessing", %processor_info); + $tx->content( + appid => '######', + type => 'VISA', + 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 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 + credit + void + +=head1 Setting vSecureProcessing parameters from content(%content) + +The following rules are applied to map data to vSecureProcessing parameters +from content(%content): + + # param => $content{} + AccountNumber => 'card_number', + Cvv => 'cvv2', + ExpirationMonth => \( $month ), # MM from MM/YY of 'expiration' + ExpirationYear => \( $year ), # YY from MM/YY of 'expiration' + Trk1 => 'track1', + Trk2 => 'track2', + CardHolderFirstName => 'first_name', + CardHolderLastName => 'last_name', + Amount => 'amount' + AvsStreet => 'address', + AvsZip => 'zip', + Cf1 => 'UDField1', + Cf2 => 'UDField2', + IndustryType => 'IndustryInfo', + +=head1 NOTE + +=head1 COMPATIBILITY + +Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014). + +See http://www.vsecureprocessing.com/ for more information. + +=head1 AUTHORS + +Original author: Alex Brelsfoard + +Current maintainer: Alex Brelsfoard + +=head1 COPYRIGHT + +Copyright (c) 2015 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. + +=head1 ADVERTISEMENT + +Need a complete, open-source back-office and customer self-service solution? +The Freeside software includes support for credit card and electronic check +processing with vSecureProcessing and over 50 other gateways, invoicing, integrated +trouble ticketing, and customer signup and self-service web interfaces. + +http://freeside.biz/freeside/ + +=head1 SEE ALSO + +perl(1). L. + +=cut + + diff --git a/t/lib/test_account.pl b/t/lib/test_account.pl new file mode 100644 index 0000000..69f8c89 --- /dev/null +++ b/t/lib/test_account.pl @@ -0,0 +1,29 @@ + +sub test_account { + # fill all these fields in to test out transactions + my %opts = ( + server =>'', # be sure to leave out the 'https://' + platform => '', + gid => '', + tid => '', + userid=> 'name@server.com', + port => 443, + env => 'test', + appid => '' + ); + + return %opts; +} + +sub expiration_date { + my($month, $year) = (localtime)[4,5]; + $month += 1; + $year++; # So we expire next year. + $year %= 100; + + return sprintf("%02d/%02d", $month, $year); +} + + +1; + diff --git a/t/t_00-load.t b/t/t_00-load.t new file mode 100644 index 0000000..a79fa22 --- /dev/null +++ b/t/t_00-load.t @@ -0,0 +1,10 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'Business::OnlinePayment::vSecureProcessing' ) || print "Bail out! +"; +} + +diag( "Testing Business::OnlinePayment::vSecureProcessing $Business::OnlinePayment::vSecureProcessing::VERSION, Perl $], $^X" ); diff --git a/t/t_boilerplate.t b/t/t_boilerplate.t new file mode 100644 index 0000000..50696af --- /dev/null +++ b/t/t_boilerplate.t @@ -0,0 +1,49 @@ +#!perl -T + +use strict; +use warnings; +use Test::More tests => 3; + +sub not_in_file_ok { + my ($filename, %regex) = @_; + open( my $fh, '<', $filename ) + or die "couldn't open $filename for reading: $!"; + + my %violated; + + while (my $line = <$fh>) { + while (my ($desc, $regex) = each %regex) { + if ($line =~ $regex) { + push @{$violated{$desc}||=[]}, $.; + } + } + } + + if (%violated) { + fail("$filename contains boilerplate text"); + diag "$_ appears on lines @{$violated{$_}}" for keys %violated; + } else { + pass("$filename contains no boilerplate text"); + } +} + +sub module_boilerplate_ok { + my ($module) = @_; + not_in_file_ok($module => + 'the great new $MODULENAME' => qr/ - The great new /, + 'boilerplate description' => qr/Quick summary of what the module/, + 'stub function definition' => qr/function[12]/, + ); +} + + not_in_file_ok(README => + "The README is used..." => qr/The README is used/, + "'version information here'" => qr/to provide version information/, + ); + + not_in_file_ok(Changes => + "placeholder date/time" => qr(Date/time) + ); + + module_boilerplate_ok('lib/Business/OnlinePayment/vSecureProcessing.pm'); + diff --git a/t/t_manifest.t b/t/t_manifest.t new file mode 100644 index 0000000..45eb83f --- /dev/null +++ b/t/t_manifest.t @@ -0,0 +1,13 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +eval "use Test::CheckManifest 0.9"; +plan skip_all => "Test::CheckManifest 0.9 required" if $@; +ok_manifest(); diff --git a/t/t_pod-coverage.t b/t/t_pod-coverage.t new file mode 100644 index 0000000..c021dd4 --- /dev/null +++ b/t/t_pod-coverage.t @@ -0,0 +1,18 @@ +use strict; +use warnings; +use Test::More skip_all => "don't care about POD coverage right now"; + +# Ensure a recent version of Test::Pod::Coverage +my $min_tpc = 1.08; +eval "use Test::Pod::Coverage $min_tpc"; +plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" + if $@; + +# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, +# but older versions don't recognize some common documentation styles +my $min_pc = 0.18; +eval "use Pod::Coverage $min_pc"; +plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" + if $@; + +all_pod_coverage_ok(); diff --git a/t/t_pod.t b/t/t_pod.t new file mode 100644 index 0000000..ee8b18a --- /dev/null +++ b/t/t_pod.t @@ -0,0 +1,12 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +# Ensure a recent version of Test::Pod +my $min_tp = 1.22; +eval "use Test::Pod $min_tp"; +plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; + +all_pod_files_ok(); diff --git a/t/t_transaction.t b/t/t_transaction.t new file mode 100644 index 0000000..401797e --- /dev/null +++ b/t/t_transaction.t @@ -0,0 +1,64 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use POSIX qw(strftime); +use Test::More; +use Business::OnlinePayment; +require "t/lib/test_account.pl"; + +my %opts = test_account_or_skip('card'); + +if (!$opt{'gid'} || !$opt{'appid'}) { + plan skip_all => "no test credentials provided; fill out t/lib/test_account.pl to test communication with the gateway.", + 1; + exit(0); +} + +plan tests => 2; + +### +# Purchase +### +my %content = ( + appid => $opts{'appid'}, + action => 'Normal Authorization', + description => 'Business::OnlinePayment visa test', + card_number => '4111111111111111', + cvv2 => '111', + expiration => expiration_date(), + amount => '24.42', + name => 'Murphy Law', + email => 'fake@acme.com', + address => '123 Anystreet', + zip => '84058', +); + +my $tx = new Business::OnlinePayment( 'vSecureProcessing', \%opts ); + +$tx->content( %content, + action => 'Normal Authorization' ); + +$tx->test_transaction(1); + +$tx->submit; + +is( $tx->is_success, 1, 'purchase' ) + or diag('Gateway error: '. $tx->error_message); + +### +# Refund +### +my $auth = $tx->authorization; +$tx = new Business::OnlinePayment( 'vSecureProcessing' ); +$tx->content( %content, + action => 'Credit', + authorization => $auth ); +$tx->test_transaction(1); + +$tx->submit; + +is( $tx->is_success, 1, 'refund' ) + or diag('Gateway error: '. $tx->error_message); + +1; diff --git a/t/t_transaction_decline.t b/t/t_transaction_decline.t new file mode 100644 index 0000000..1309c78 --- /dev/null +++ b/t/t_transaction_decline.t @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use POSIX qw(strftime); +use Test::More; +use Business::OnlinePayment; +require "t/lib/test_account.pl"; + +my %opts = test_account_or_skip('card'); + +if (!$opt{'gid'} || !$opt{'appid'}) { + plan skip_all => "no test credentials provided; fill out t/lib/test_account.pl to test communication with the gateway.", + 1; + exit(0); +} + +plan tests => 2; +my %content = ( + appid => $opts{'appid'}, + action => 'Normal Authorization', + description => 'Business::OnlinePayment visa test', + card_number => '4111111111111112', # trigger failure + cvv2 => '111', + expiration => expiration_date(), + amount => '24.42', + name => 'Murphy Law', + email => 'fake@acme.com', + address => '123 Anystreet', + zip => '84058', +); + +my $tx = new Business::OnlinePayment( 'vSecureProcessing', \%opts ); + +$tx->content( %content ); + +$tx->test_transaction(1); + +$tx->submit; + +is( $tx->is_success, 0, 'declined purchase') + or diag('Test transaction should have failed, but succeeded'); +is( $tx->failure_status, 'nsf', 'failure status' ) + or diag('Failure status reported as '.$tx->failure_status); + +1; diff --git a/test.pl b/test.pl deleted file mode 100755 index 40f528b..0000000 --- a/test.pl +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/perl -w - -# -# Make sure to copy Business::OnlinePayment::vSecureProcessing into its -# proper system directory (aka /usr/share/perl5/Business/Onlinepayment) -# - -use strict; -use Business::OnlinePayment; - -my %opt = ( - server =>'dvrotsos2.kattare.com', - platform => 'Buypass', - gid => '1432479912596791', - tid => '01', - userid=> 'tom@yiptv.com', - port => 443, - env => 'test' -); - -my $action = shift || 'Normal Authorization'; -my $auth = shift || ''; - - -my %content = ( - appid => 'yiptv', - action => $action, - auth => $auth, - description => 'Business::OnlinePayment visa test', -# card_number => '4007000000027', - card_number => '4111111111111111', - cvv2 => '111', - expiration => expiration_date(), - amount => '42.24', - name => 'Murphy Law', - email => 'fake@acme.com', - address => '123 Anystreet', - zip => '84058', -); - -main(); - -sub main { - my $transaction = Business::OnlinePayment->new("vSecureProcessing", %opt); - - print "MAKING PAYMENT\n"; - MakPayment($transaction); - $content{'action'} = 'void'; - $content{'reference_number'} = $transaction->authorization; - $content{'transaction_date'} = $transaction->txn_date; - $content{'amount'} = $transaction->txn_amount; - print "VOIDING PAYMENT\n"; - VoidPayment($transaction); - $content{'action'} = 'Normal Authorization'; - print "MAKING PAYMENT\n"; - MakePayment($transaction); - print "REFUNDING PAYMENT\n"; - $content{'action'} = 'credit'; - $content{'reference_number'} = $transaction->authorization; - $content{'transaction_date'} = $transaction->txn_date; - $content{'amount'} = $transaction->txn_amount; - refundPayment($transaction); -} - -sub MakePayment { - $transaction->content(%content); - - eval { $transaction->submit(); }; - - if ( $@ ) { - - print "Error: $@\n"; - - } else { - - if ( $transaction->is_success() ) { - print "Card processed successfully: ". $transaction->authorization()."\n"; - } else { - print "Card was rejected: ". $transaction->error_message(). "\n"; - } - } -} - -sub VoidPayment { - -} - -sub RefundPayment { - -} - - -sub expiration_date { - my($month, $year) = (localtime)[4,5]; - $month += 1; - $year++; # So we expire next year. - $year %= 100; # y2k? What's that? - - return sprintf("%02d/%02d", $month, $year); -} diff --git a/vSecureProcessing.pm b/vSecureProcessing.pm deleted file mode 100644 index 3c73067..0000000 --- a/vSecureProcessing.pm +++ /dev/null @@ -1,689 +0,0 @@ -package Business::OnlinePayment::vSecureProcessing; - -use strict; -use Carp; -use Template; -use XML::Simple; -use Data::Dumper; - -use Business::OnlinePayment; -use Business::OnlinePayment::HTTPS; -#use Net::SSLeay qw(post_http post_https make_headers make_form); -use vars qw($VERSION $DEBUG @ISA $me); - -@ISA = qw(Business::OnlinePayment::HTTPS); -$DEBUG = 0; -$VERSION = '0.01'; -$me = 'Business::OnlinePayment::vSecureProcessing'; - - -# $server: http://dvrotsos2.kattare.com - -# mapping out all possible endpoints -# but this version will only be building out "charge", "void", & "credit" -my %payment_actions = ( - 'charge' => { - path => '/vsg2/processpayment', - }, - 'void' => { - path => '/vsg2/processvoid', - }, - 'refund' => { - path => '/vsg2/processrefund', - }, - 'authorize' => { - path => '/vsg2/processauth', - }, - 'authorize_cancel' => { - path => '/vsg2/processauthcancel', - }, - 'capture' => { - path => '/vsg2/processcaptureonly', - }, - 'create_token' => { - path => '/vsg2/createtoken', - }, - 'delete_token' => { - path => '/vsg2/deletetoken', - }, - 'query_token' => { - path => '/vsg2/querytoken', - }, - 'update_exp_date' => { - path => '/vsg2/updateexpiration', - }, - 'update_token' => { - path => '/vsg2/updatetoken', - }, - -); - -my %action_mapping = ( - 'normal authorization' => 'charge', - 'credit' => 'refund', - 'authorization only' => 'authorize', - 'post authorization' => 'capture', - 'reverse authorization' => 'authorize_cancel' - # void => void -); - -sub set_defaults { - my $self = shift; - my %options = @_; - - # inistialize standard B::OP attributes - $self->is_success(0); - $self->$_( '' ) for qw/authorization - result_code - error_message - server - port - path - server_response/; - - # B::OP creates the following accessors: - # server, port, path, test_transaction, transaction_type, - # server_response, is_success, authorization, - # result_code, error_message, - - $self->build_subs(qw/ - env platform userid gid tid appid action cvv_response - avs_response risk_score txn_amount txn_date - /); - - $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG; - - - - $self->server($options{'server'}); - - $self->gid($options{'gid'}); - - $self->tid($options{'tid'}); - - $self->platform($options{'platform'}); - - $self->appid($options{'appid'}); - - $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test' - - $self->port(443); -} - - - -sub clean_content { - my ($self,$content) = @_; - my %content = $self->content(); - - { - no warnings 'uninitialized'; - - # strip non-digits from card number - my $card_number = ''; - if ( $content{card_number} ) { - $content{card_number} =~ s/\D//g; - } - - # separate month and year values for expiry_date - if ( $content{expiration} ) { - ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration}; - $content{exp_month} = sprintf "%02d", $content{exp_month}; - $content{exp_year} = substr($content{exp_year},0,2) if ($content{exp_year} > 99); - } - - if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) { - ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2); - } - - if ($content{'address'} =~ m/[\D ]*(\d+)\D/) { - $content{'street_number'} = $1; - } - } - warn "Content after cleaning:\n".Dumper(\%content)."\n" if ($DEBUG >2); - $self->content(%content); -} - -sub process_content { - my $self = shift; - $self->clean_content(); - my %content = $self->content(); - $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'}); - $self->path($payment_actions{ $self->action }{path}) - unless length($self->path); - $self->appid($content{appid}) if (!$self->appid && $content{appid}); -} - -sub submit { - my $self = shift; - - # inistialize standard B::OP attributes - $self->is_success(0); - $self->$_( '' ) for qw/authorization - result_code - error_message - server_response/; - - # clean and process the $self->content info - $self->process_content(); - my %content = $self->content; - my $action = $self->action(); - - my @acceptable_actions = ('charge', 'refund', 'void'); - - unless ( grep { $action eq $_ } @acceptable_actions ) { - croak "'$action' is not supported at this time."; - } - - # fill out the template vars - my $template_vars = { - - auth => { - platform => $self->platform, - userid => $self->userid, - gid => $self->gid, - tid => $self->tid - }, - - payment => { - amount => $content{'amount'}, - track1 => ($content{'track1'}) ? $content{'track1'} : '', - track2 => ($content{'track2'}) ? $content{'track2'} : '', - type => ($content{'description'}) ? $content{'description'} : '', - cf1 => ($content{'UDField1'}) ? $content{'UDField1'} : '', - cf2 => ($content{'UDField2'}) ? $content{'UDField2'} : '', - cf3 => '', - account_number => ($content{'card_number'}) ? $content{'card_number'} : '', - exp_month => $content{'exp_month'}, - exp_year => $content{'exp_year'}, - cvv => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '', - first_name => ($content{'first_name'}) ? $content{'first_name'} : '', - last_name => ($content{'last_name'}) ? $content{'last_name'} : '', - postal_code => ($content{'zip'}) ? $content{'zip'} : '', - street_address => ($content{'street_number'}) ? $content{'street_number'} : '', - industry_type => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '', - invoice_num => ($content{'invoice_number'}) ? $content{'invoice_number'} : '', - appid => $self->appid(), - recurring => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0, - response_code => ($content{'response_code'}) ? $content{'response_code'} : '', - reference_number=> ($content{'ref_num'}) ? $content{'ref_num'} : '', - token => ($content{'token'}) ? $content{'token'} : '', - receipt => ($content{'receipt'}) ? $content{'receipt'} : '', - transaction_date=> ($content{'txn_date'}) ? $content{'txn_date'} : '', - merchant_data => ($content{'merchant_data'}) ? $content{'merchant_data'} : '', - }, - - # we won't be using level2 nor level3. So I'm leaving them blank for now. - level2 => { - card_type => '', - purchase_code => '', - country_code => '', - ship_tp_postal_code => '', - ship_from_postal_code => '', - sales_tax => '', - product_description1 => '', - product_description2 => '', - product_description3 => '', - product_description4 => '' - }, - - level3 => { - purchase_order_num => '', - order_date => '', - duty_amount => '', - alt_tax_amount => '', - discount_amount => '', - freight_amount => '', - tax_exempt => '', - line_item_count => '', - purchase_items => $self->_parse_line_items() - } - }; - - - # create the list of required fields based on the action - my @required_fields = qw/ amount /; - if ($action eq 'charge') { - push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /); - }elsif ($action eq 'void') { - push(@required_fields, $_) foreach (qw/ reference_number transaction_date /); - }elsif ($action eq 'refund') { - push(@required_fields, $_) foreach (qw/ amount account_number exp_month exp_year /); - } - - # check the requirements are met. - my @missing_fields; - foreach my $field (@required_fields) { - push(@missing_fields, $field) if (!$template_vars->{payment}{$field}); - } - if (scalar(@missing_fields)) { - croak "Missing required fields: ".join(', ', @missing_fields); - } - - # read in the appropriate xml template - my $xml_template = _get_xml_template( $action ); - # create a template object. - my $tt = Template->new(); - # populate the XML template. - my $xml_data; - $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error(); - - warn "XML:\n$xml_data\n" if $DEBUG > 2; - - my $boundary = sprintf('FormBoundary%06d', int(rand(1000000))); - # opts for B:OP:HTTPS::https_post - my $opts = { headers => {}}; - $opts->{'Content-Type'} = - $opts->{headers}->{'Content-Type'} = - "multipart/form-data, boundary=$boundary"; - - my $content = - "--$boundary\n". - "Content-Disposition: form-data; name=\"param\"\n\n". - $xml_data."\n". - "--$boundary--\n"; - - # conform to RFC standards - $content =~ s/\n/\r\n/gs; - - my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content ); - - # store the server response. - $self->server_response($server_response); - # parse the result page. - $self->parse_response($page); - - if (!$self->is_success() && !$self->error_message() ) { - if ( $DEBUG ) { - #additional logging information, possibly too sensitive for an error msg - # (vSecureProcessing seems to have a failure mode where they return the full - # original request including card number) - $self->error_message( - "(HTTPS response: ".$server_response.") ". - "(HTTPS headers: ". - join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ". - "(Raw HTTPS content: ".$page.")" - ); - } else { - my $response_code = $self->response_code() || ''; - if ($response_code) { - $self->error_message(qq|Error code ${response_code} was returned by vSecureProcessing. (enable debugging for raw HTTPS response)|); - }else { - $self->error_message('No error information was returned by vSecureProcessing (enable debugging for raw HTTPS response)'); - } - } - } - -} - -# read $self->server_response and decipher any errors -sub parse_response { - my $self = shift; - my $page = shift; - - if ($self->server_response =~ /^200/) { - my $response = XMLin($page); - warn "RESPONSE: \n".Dumper($response)."\n"; - $self->result_code($response->{Status}); - $self->avs_response($response->{AvsResponse}); - $self->cvv_response($response->{CvvResponse}); - $self->txn_date($response->{TransactionDate}); - $self->txn_amount($response->{TransactionAmount} / 100); - $self->cvv_response($response->{CvvResponse}); - $self->is_success($self->result_code() eq '0' ? 1 : 0); - if ($self->is_success()) { - $self->authorization($response->{AuthIdentificationResponse}); - } - # fill in error_message if there is is an error - if ( !$self->is_success && exists($response->{ResultCode})) { - $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{ResultCode}); - }elsif ( !$self->is_success && exists($response->{Receipt}) ) { - $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : ''); - } - - }else { - $self->is_success(0); - $self->error_message('Error communicating with vSecureProcessing server'); - return; - } - - -} - -sub _get_xml_template { - my $action = shift; - - my $xml_template = q| - - [% auth.platform %] - [% auth.userid %] - [% auth.gid %] - [% auth.tid %] - - |; - - if ($action eq 'charge') { - $xml_template .= _get_xml_template_charge(); - }elsif($action eq 'void') { - $xml_template .= _get_xml_template_void(); - }elsif($action eq 'authorize') { - $xml_template .= _get_xml_template_auth(); - }elsif($action eq 'authorize_cancel') { - $xml_template .= _get_xml_template_auth_cancel(); - }elsif($action eq 'refund') { - $xml_template .= _get_xml_template_refund(); - }elsif($action eq 'capture') { - $xml_template .= _get_xml_template_capture(); - }elsif($action eq 'create_token') { - $xml_template .= _get_xml_template_create_token(); - }elsif($action eq 'delete_token') { - $xml_template .= _get_xml_template_delete_token(); - }elsif($action eq 'query_token') { - $xml_template .= _get_xml_template_query_token(); - }elsif($action eq 'update_exp_date') { - $xml_template .= _get_xml_template_update_exp_date(); - }elsif($action eq 'update_token') { - $xml_template .= _get_xml_template_update_token(); - } - - $xml_template .= ""; - $xml_template =~ s/[\n\t\s]*//g; - - return $xml_template; -} - -sub _get_xml_template_charge { - my $xml_template = q| - [% payment.amount %] - [% payment.track1 %] - [% payment.track2 %] - [% payment.type %] - [% payment.cf1 %] - [% payment.cf2 %] - [% payment.cf3 %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.cvv %] - [% payment.first_name %] - [% payment.last_name %] - [% payment.postal_code %] - [% payment.street_address %] - - [% payment.industry_type %] - [% payment.invoice_num %] - - [% payment.appid %] - [% payment.recurring %] - |; - - # other options (that we are not using right now): -# -# [% level2.card_type %] -# [% level2.purchase_code %] -# [% level2.country_code %] -# [% level2.ship_tp_postal_code %] -# [% level2.ship_from_postal_code %] -# [% level2.sales_tax %] -# [% level2.product_description1 %] -# [% level2.product_description2 %] -# [% level2.product_description3 %] -# [% level2.product_description4 %] -# -# -# [% level3.purchase_order_num %] -# [% level3.order_date %] -# [% level3.duty_amount %] -# [% level3.alt_tax_amount %] -# [% level3.discount_amount %] -# [% level3.freight_amount %] -# [% level3.tax_exempt %] -# [% level3.line_item_count %] -# -# [% level3.purchase_items %] -# -# - - return $xml_template; -} - -sub _parse_line_items { - my $self = shift; - my %content = $self->content(); - - return '' if (!$content{'items'}); - - my @line_items; - my $template = q| - [% seq_num %] - [% code %] - [% desc %] - [% qty %] - [% unit %] - [% unit_cost %] - [% amount %] - [% discount_amount %] - [% tax_amount %] - [% tax_rate %] - |; - - - my @items = $content{'items'}; - foreach my $item (@items) { - # fille in the slots from $template with details in $item - # push to @line_items - } - - return join("\n", @line_items); -} - -sub _get_xml_template_void { - my $xml_template = q| - [% payment.amount %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.reference_number %] - - [% payment.industry_type %] - [% payment.appid %] - |; - - return $xml_template; -} - -sub _get_xml_template_refund { - my $xml_template = q| - [% payment.amount %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.appid %] - |; - - return $xml_template; -} - -sub _get_xml_template_auth { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_auth_cancel { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_capture { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_create_token { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_delete_token { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_query_token { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_update_exp_date { - my $xml_template = ''; - - return $xml_template; -} - -sub _get_xml_template_update_token { - my $xml_template = ''; - - return $xml_template; -} - - -1; -__END__ - - -=head1 NAME - -Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Business::OnlinePayment - -=head1 SYNOPSIS - - use Business::OnlinePayment; - my %processor_info = ( - platform => '####', - gid => 12345678901234567890, - tid => 01, - user_id => '####', - url => 'www.####.com' - ); - my $tx = - new Business::OnlinePayment( "vSecureProcessing", %processor_info); - $tx->content( - appid => '######', - type => 'VISA', - 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 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 - credit - void - -=head1 Setting vSecureProcessing parameters from content(%content) - -The following rules are applied to map data to vSecureProcessing parameters -from content(%content): - - # param => $content{} - AccountNumber => 'card_number', - Cvv => 'cvv2', - ExpirationMonth => \( $month ), # MM from MM/YY of 'expiration' - ExpirationYear => \( $year ), # YY from MM/YY of 'expiration' - Trk1 => 'track1', - Trk2 => 'track2', - CardHolderFirstName => 'first_name', - CardHolderLastName => 'last_name', - Amount => 'amount' - AvsStreet => 'address', - AvsZip => 'zip', - Cf1 => 'UDField1', - Cf2 => 'UDField2', - IndustryType => 'IndustryInfo', - -=head1 NOTE - -=head1 COMPATIBILITY - -Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014). - -See http://www.vsecureprocessing.com/ for more information. - -=head1 AUTHORS - -Original author: Alex Brelsfoard - -Current maintainer: Alex Brelsfoard - -=head1 COPYRIGHT - -Copyright (c) 2015 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. - -=head1 ADVERTISEMENT - -Need a complete, open-source back-office and customer self-service solution? -The Freeside software includes support for credit card and electronic check -processing with vSecureProcessing and over 50 other gateways, invoicing, integrated -trouble ticketing, and customer signup and self-service web interfaces. - -http://freeside.biz/freeside/ - -=head1 SEE ALSO - -perl(1). L. - -=cut - -