From: fbriere Date: Mon, 13 Feb 2006 21:10:31 +0000 (+0000) Subject: Initial import X-Git-Tag: V0_01~59 X-Git-Url: http://git.freeside.biz/gitweb/?p=Business-OnlinePayment-InternetSecure.git;a=commitdiff_plain;h=7af3e137d97474aa364a585359bbfb400f0594cf Initial import --- 7af3e137d97474aa364a585359bbfb400f0594cf diff --git a/Changes b/Changes new file mode 100755 index 0000000..b543b30 --- /dev/null +++ b/Changes @@ -0,0 +1,6 @@ +Revision history for Perl extension Business::OnlinePayment::InternetSecure. + +0.01 Sat Jul 10 16:33:05 2004 + - original version; created by h2xs 1.23 with options + -X -A -n Business::OnlinePayment::InternetSecure -b 5.8.0 + diff --git a/InternetSecure.pm b/InternetSecure.pm new file mode 100755 index 0000000..a6240bc --- /dev/null +++ b/InternetSecure.pm @@ -0,0 +1,562 @@ +package Business::OnlinePayment::InternetSecure; + +use 5.008; +use strict; +use warnings; + +use Carp; +use Encode; +use Net::SSLeay qw(make_form post_https); +use XML::Simple qw(xml_in xml_out); + +use base qw(Business::OnlinePayment Exporter); + + +our $VERSION = '0.01'; + + +use constant CARD_TYPES => { + VI => 'Visa', + MC => 'MasterCard', + AX => 'American Express', + NN => 'Discover', + }; + + +# Convenience functions to avoid undefs and escape products strings +sub _def($) { defined $_[0] ? $_[0] : '' } +sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ } + + +sub set_defaults { + my ($self) = @_; + + $self->server('secure.internetsecure.com'); + $self->port(443); + $self->path('/process.cgi'); + + $self->build_subs(qw( + receipt_number sales_order_number + cardholder card_type + total_amount + avs_response cvv2_response + )); +} + +# OnlinePayment's remap_fields is buggy, so we simply rewrite it +# +sub remap_fields { + my ($self, %map) = @_; + + my %content = $self->content(); + foreach (keys %map) { + $content{$map{$_}} = delete $content{$_}; + } + $self->content(%content); +} + +# Combine get_fields and remap_fields for convenience +# +sub get_remap_fields { + my ($self, %map) = @_; + + $self->remap_fields(reverse %map); + my %data = $self->get_fields(keys %map); + + foreach (values %data) { + $_ = '' unless defined; + } + + return %data; +} + +# Since there's no standard format for expiration dates, we try to do our best +# +sub parse_expdate { + my ($self, $str) = @_; + + local $_ = $str; + + my ($y, $m); + + if (/^(\d{4})\W(\d{1,2})$/ || # 2004.07 or 2004-7 + /^(\d\d)\W(\d)$/ || # 04/7 + /^(\d\d)[.-](\d\d)$/) { # 04-07 + ($y, $m) = ($1, $2); + } elsif (/^(\d{1,2})\W(\d{4})$/ || # 07-2004 or 7/2004 + /^(\d)\W(\d\d)$/ || # 7/04 + /^(\d\d)\/(\d\d)$/) { # 07/04 + ($y, $m) = ($2, $1); + } else { + croak "Unable to parse expiration date: $str"; + } + + $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us? + + return ($y, $m); +} + +# Convert a single product into a product string +# +sub prod_string { + my ($self, $currency, $taxes, %data) = @_; + + croak "Missing amount in product" unless defined $data{amount}; + + my @flags = ($currency); + + $taxes = uc $data{taxes} if defined $data{taxes}; + foreach (split ' ' => $taxes) { + croak "Unknown tax code $_" unless /^(GST|PST|HST)$/; + push @flags, $_; + } + + if ($self->test_transaction) { + push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST'; + } + + return join '::' => + sprintf('%.2f' => $data{amount}), + $data{quantity} || 1, + _esc _def $data{sku}, + _esc _def $data{description}, + join('' => map "{$_}" => @flags), + ; +} + +# Generate the XML document for this transaction +# +sub to_xml { + my ($self) = @_; + + my %content = $self->content; + + $self->required_fields(qw(action card_number exp_date)); + + croak 'Unsupported transaction type' + if $content{type} && $content{type} !~ + /^(Visa|MasterCard|American Express|Discover)$/i; + + croak 'Unsupported action' + unless $content{action} =~ /^Normal Authori[zs]ation$/i; + + $content{currency} ||= 'CAD'; + $content{currency} = uc $content{currency}; + croak "Unknown currency code ", $content{currency} + unless $content{currency} =~ /^(CAD|USD)$/; + + $content{taxes} ||= ''; + $content{taxes} = uc $content{taxes}; + + my %data = $self->get_remap_fields(qw( + xxxCardNumber card_number + + xxxName name + xxxCompany company + xxxAddress address + xxxCity city + xxxProvince state + xxxPostal zip + xxxCountry country + xxxPhone phone + xxxEmail email + + xxxShippingName ship_name + xxxShippingCompany ship_company + xxxShippingAddress ship_address + xxxShippingCity ship_city + xxxShippingProvince ship_state + xxxShippingPostal ship_zip + xxxShippingCountry ship_country + xxxShippingPhone ship_phone + xxxShippingEmail ship_email + )); + + $data{MerchantNumber} = $self->merchant_id; + + $data{xxxCardNumber} =~ tr/ //d; + + my ($y, $m) = $self->parse_expdate($content{exp_date}); + $data{xxxCCYear} = sprintf '%.4u' => $y; + $data{xxxCCMonth} = sprintf '%.2u' => $m; + + if (defined $content{cvv2} && $content{cvv2} ne '') { + $data{CVV2} = 1; + $data{CVV2Indicator} = $content{cvv2}; + } else { + $data{CVV2} = 0; + $data{CVV2Indicator} = ''; + } + + if (ref $content{description}) { + $data{Products} = join '|' => map $self->prod_string( + $content{currency}, + $content{taxes}, + %$_), + @{ $content{description} }; + } else { + $self->required_fields(qw(amount)); + $data{Products} = $self->prod_string( + $content{currency}, + $content{taxes}, + amount => $content{amount}, + description => $content{description}, + ); + } + + xml_out(\%data, + NoAttr => 1, + RootName => 'TranxRequest', + XMLDecl => '', + ); +} + +# Map the various fields from the response, and put their values into our +# object for retrieval. +# +sub infuse { + my ($self, $data, %map) = @_; + + while (my ($k, $v) = each %map) { + no strict 'refs'; + $self->$v($data->{$k}); + } +} + +# Parse the server's response and set various fields +# +sub parse_response { + my ($self, $response) = @_; + + $self->server_response($response); + + # (It's not quite clear whether there should be some decoding, or if + # the result is already utf8.) + + $response = xml_in($response, + ForceArray => [qw(product flag)], + GroupTags => { qw(Products product flags flag) }, + KeyAttr => [], + SuppressEmpty => undef, + ); + + my $code = $self->result_code($response->{Page}); + $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1'); + + $self->infuse($response, qw( + ReceiptNumber receipt_number + SalesOrderNumber sales_order_number + xxxName cardholder + CardType card_type + Page result_code + ApprovalCode authorization + Verbiage error_message + TotalAmount total_amount + AVSResponseCode avs_response + CVV2ResponseCode cvv2_response + )); + + $self->card_type(CARD_TYPES->{$self->card_type}); + + $self->{products_raw} = $response->{Products}; + + return $self; +} + +sub submit { + my ($self) = @_; + + croak "Missing required argument 'merchant_id'" + unless defined $self->{merchant_id}; + + my ($page, $response, %headers) = + post_https( + $self->server, + $self->port, + $self->path, + undef, + make_form( + xxxRequestMode => 'X', + xxxRequestData => Encode::encode_utf8( + $self->to_xml + ), + ) + ); + + croak 'Error connecting to server' unless $page; + croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/; + + $self->parse_response($page); +} + + +1; + +__END__ + + +=head1 NAME + +Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment + +=head1 SYNOPSIS + + use Business::OnlinePayment; + + $txn = new Business::OnlinePayment 'InternetSecure', + merchant_id => '0000'; + + $txn->content( + action => 'Normal Authorization', + + type => 'Visa', + card_number => '0000000000000000', + exp_date => '2004-07', + cvv2 => '000', # Optional + + name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re", + company => '', + address => '123 Street', + city => 'Metropolis', + state => 'ZZ', + zip => 'A1A 1A1', + country => 'CA', + phone => '(555) 555-1212', + email => 'fbriere@fbriere.net', + + description => 'Online purchase', + amount => 49.95, + currency => 'CAD', + taxes => 'GST PST', + ); + + $txn->submit; + + if ($txn->is_success) { + print "Card processed successfully: " . $tx->authorization . "\n"; + } else { + print "Card was rejected: " . $tx->error_message . "\n"; + } + +=head1 DESCRIPTION + +Business::OnlinePayment::InternetSecure is an implementation of +L that allows for processing online credit card +payments through Internet Secure. + +See L for more information about the generic +Business::OnlinePayment interface. + +=head1 CREATOR + +Object creation is done via L; see its manpage for +details. The I processor option is required, and corresponds +to the merchant ID assigned to you by Internet Secure. + +=head1 METHODS + +(See L for more methods.) + +=head2 Before order submission + +=over 4 + +=item content( CONTENT ) + +Sets up the data prior to a transaction (overwriting any previous data by the +same occasion). CONTENT is an associative array (hash), containing some of +the following fields: + +=over 4 + +=item action (required) + +What to do with the transaction. Only C is supported +for the moment. + +=item type + +Transaction type, being one of the following: + +=over 4 + +=item - Visa + +=item - MasterCard + +=item - American Express + +=item - Discover + +=back + +(This is actually ignored for the moment, and can be left blank or undefined.) + +=item card_number (required) + +Credit card number. Any spaces will be removed. + +=item exp_date (required) + +Credit card expiration date. Since L does not +specify any syntax, this module is rather lax in what it will accept. It is +recommended to use either I or I, to avoid any nasty +surprises. + +=item cvv2 + +Three- or four-digit verification code printed on the card. This can be left +blank or undefined, in which case no check will be performed. Whether or not a +transaction will be declined in case of a mismatch depends on the merchant. + +This number may be called Card Verification Value (CVV2), Card Validation +Code (CVC2) or Card Identification number (CID). + +=item description + +A short description of the purchase. See L<"Products list syntax"> for +an alternate syntax that allows a list of products to be specified. + +=item amount + +Total amount to be billed, excluding taxes if they are to be added separately. +This field is required if B is a string, and should be left +undefined if B contains a list of products, as outlined in +L<"Products list syntax">. + +=item currency + +Currency of all amounts for this order. This can currently be either +C (default) or C. + +=item taxes + +Taxes to be added automatically. These should not be included in B; +they will be added by Internet Secure later on. + +Available taxes are C, C and C; multiple taxes must be +separated by spaces. + +=item name / company / address / city / state / zip / country / phone / email + +Facultative customer information. B should be either a postal +abbreviation or a two-letter code taken from ISO 3166-2, and B should +be a two-letter code taken from in ISO 3166-1. + +=back + +=back + +=head2 After order submission + +=over 4 + +=item receipt_number() / sales_order_number() + +Receipt number and sales order number of submitted order. + +=item total_amount() + +Total amount billed for this order, including taxes. + +=item cardholder() + +Cardholder's name. This is currently a mere copy of the B field passed +to B. + +=item card_type() + +Type of the credit card used for the submitted order, being one of the +following: + +=over 4 + +=item - Visa + +=item - MasterCard + +=item - American Express + +=item - Discover + +=back + +=item avs_response() / cvv2_response() + +Results of the AVS and CVV2 checks. See the Internet Secure documentation for +the list of possible values. + +=back + + +=head1 NOTES + +=head2 Products list syntax + +Optionally, the B field of B can contain a reference +to an array of products, instead of a simple string. Each element of this +array represents a different product, and must be a reference to a hash with +the following fields: + +=over 4 + +=item amount + +Unit price of this product. + +=item quantity + +Ordered quantity of this product. This can be a decimal value. + +=item sku + +Internal code for this product. + +=item description + +Description of this product + +=item taxes + +Taxes that should be automatically added to this product. If specified, this +overrides the B field passed to B. + +=back + +When using a products list, the B field passed to B should +be left undefined. + + +=head2 Character encodings + +... + + +=head2 products_raw + +... + + +=head1 EXPORT + +None by default. + + +=head1 SEE ALSO + +L + +=head1 AUTHOR + +Frederic Briere, Efbriere@fbriere.netE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2004 by Frederic Briere + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.4 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/MANIFEST b/MANIFEST new file mode 100755 index 0000000..756d53e --- /dev/null +++ b/MANIFEST @@ -0,0 +1,9 @@ +InternetSecure.pm +Changes +Makefile.PL +MANIFEST +README +t/10base.t +t/20emit.t +t/30parse.t +t/40live.t diff --git a/Makefile.PL b/Makefile.PL new file mode 100755 index 0000000..e59aed9 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,14 @@ +use 5.008; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Business::OnlinePayment::InternetSecure', + AUTHOR => 'Frederic Briere ', + VERSION_FROM => 'InternetSecure.pm', + ABSTRACT_FROM => 'InternetSecure.pm', + PREREQ_PM => { + 'Business::OnlinePayment' => 0, + 'Net::SSLeay' => 0, + 'XML::Simple' => 0, + }, +); diff --git a/README b/README new file mode 100755 index 0000000..699e66e --- /dev/null +++ b/README @@ -0,0 +1,42 @@ +Business-OnlinePayment-InternetSecure version 0.01 +================================================== + + This is Business::OnlinePayment::BankOfAmerica, a Business::OnlinePayment + backend module for Bank of America eStores. It is only useful if you + have a merchant account with Bank of America eStores: + http://www.bofa.com/merchantservices/index.cfm?template=merch_ic_estores_ov.cfm + + It based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles. + +archive can use it get an idea of the modules uses. It is usually a +good idea to provide version information here so that people can +decide whether fixes for the module are worth downloading. + +INSTALLATION + +To install this module type the following: + + perl Makefile.PL + make + make test + make install + + Online tests + +DEPENDENCIES + +This module requires these other modules and libraries: + + Depends on B:OP, Net::SSLeay and XML::Simple + +COPYRIGHT AND LICENCE + +Put the correct copyright and licence information here. + +Copyright (C) 2004 by Frederic Briere + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.4 or, +at your option, any later version of Perl 5 you may have available. + + diff --git a/t/10base.t b/t/10base.t new file mode 100755 index 0000000..2f956f2 --- /dev/null +++ b/t/10base.t @@ -0,0 +1,4 @@ +use Test::More tests => 2; + +BEGIN { use_ok('Business::OnlinePayment') }; +BEGIN { use_ok('Business::OnlinePayment::InternetSecure') }; diff --git a/t/20emit.t b/t/20emit.t new file mode 100755 index 0000000..a162f10 --- /dev/null +++ b/t/20emit.t @@ -0,0 +1,143 @@ +use Test::More tests => 4 + 2; + +BEGIN { use_ok('Business::OnlinePayment') }; +BEGIN { use_ok('Business::OnlinePayment::InternetSecure') }; +BEGIN { use_ok('XML::Simple', qw(xml_in)) }; +BEGIN { use_ok('Encode') }; + +use charnames ':full'; # Why doesn't this work with use_ok? + +use constant TRANSACTIONS => ( + { + _test => 0, + + action => 'Normal Authorization', + + type => 'Visa', + card_number => '0000000000000000', + exp_date => '2004-07', + cvv2 => '000', + + name => "Fr\N{LATIN SMALL LETTER E WITH ACUTE}d\N{LATIN SMALL LETTER E WITH ACUTE}ric Bri\N{LATIN SMALL LETTER E WITH GRAVE}re", + company => '', + address => '123 Street', + city => 'Metropolis', + state => 'ZZ', + zip => 'A1A 1A1', + country => 'CA', + phone => '(555) 555-1212', + email => 'fbriere@fbriere.net', + + amount => undef, + currency => 'CAD', + taxes => 'GST PST', + + description => [ + { + amount => 9.99, + quantity => 5, + sku => 'a:001', + description => 'Some product', + }, + { + amount => 5.65, + description => 'Shipping', + }, + { + amount => 10.00, + description => 'Some HST example', + taxes => 'HST', + }, + ], + }, + { + _test => 1, + + action => 'Normal Authorization', + + type => 'Visa', + card_number => '0000000000000000', + exp_date => '7/2004', + + name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re", + + amount => 12.95, + currency => 'USD', + taxes => '', + description => "Box o' goodies", + }, +); + + +my $txn = new Business::OnlinePayment 'InternetSecure', merchant_id => '0000'; + +$/ = ''; +foreach (TRANSACTIONS) { + $txn->test_transaction(delete $_->{_test}); + $txn->content(%$_); + is_deeply( + xml_in(Encode::encode('utf8', $txn->to_xml)), + xml_in(scalar ) + ); +} + + +__DATA__ + + + 0000 + 0000000000000000 + 07 + 2004 + 1 + 000 + 9.99::5::a 001::Some product::{CAD}{GST}{PST}|5.65::1::::Shipping::{CAD}{GST}{PST}|10.00::1::::Some HST example::{CAD}{HST} + Frédéric Brière + + 123 Street + Metropolis + ZZ + A1A 1A1 + CA + (555) 555-1212 + fbriere@fbriere.net + + + + + + + + + + + + + + 0000 + 0000000000000000 + 07 + 2004 + 0 + + 12.95::1::::Box o' goodies::{USD}{TEST} + Frédéric Brière + + + + + + + + + + + + + + + + + + + diff --git a/t/30parse.t b/t/30parse.t new file mode 100755 index 0000000..8780731 --- /dev/null +++ b/t/30parse.t @@ -0,0 +1,163 @@ +use Test::More tests => 1 + 2 * 5; + +BEGIN { use_ok('Business::OnlinePayment') }; + + +use constant FIELDS => (qw( result_code authorization total_amount )); + +use constant RESULTS => ( + [ 1, '2000', 'T00000', 3.88 ], + [ 0, '98e05', undef, 3.88 ], + ); + + +my $txn = new Business::OnlinePayment 'InternetSecure', merchant_id => '0000'; + +$/ = ''; +foreach (RESULTS) { + my @results = @$_; + + my $xml = ; + $txn->parse_response($xml); + + is($txn->server_response, $xml, 'server_response'); + + if (shift @results) { + ok($txn->is_success, 'expecting success'); + } else { + ok(!$txn->is_success, 'expecting failure'); + } + + foreach (FIELDS) { + no strict 'refs'; + is($txn->$_, shift @results, $_); + } +} + + +__DATA__ + + + 4994 + 1096019995.5012 + 0 + John Smith + 2003/12/17 09:59:58 + Test Card Number + 2000 + T00000 + Test Approved + 3.88 + + + 001 + Test Product 1 + 1 + 3.10 + 3.10 + + {USD} + {GST} + {TEST} + + + + 010 + Test Product 2 + 1 + 0.20 + 0.20 + + {GST} + {TEST} + + + + 020 + Test Product 3 + 1 + 0.33 + 0.33 + + {GST} + {TEST} + + + + GST + Canadian GST Charged + 1 + 0.25 + 0.25 + + {TAX} + {CALCULATED} + + + + 3.10::1::001::Test Product 1::{USD}{GST}{TEST}|0.20::1::010::Test Product 2::{GST}{TEST}|0.33::1::020::Test Product 3::{GST}{TEST}|0.25::1::GST::Canadian GST Charged::{TAX}{CALCULATED} + + + + + + + 4994 + 1096021915.5853 + 729 + John Smith + 2003/12/17 10:31:58 + VI + 98e05 + + Incorrect Card Number - Please Retry + 3.88 + + + 001 + Test Product 1 + 1 + 3.10 + 3.10 + + {USD} + {GST} + + + + 010 + Test Product 2 + 1 + 0.20 + 0.20 + + {GST} + + + + 020 + Test Product 3 + 1 + 0.33 + 0.33 + + {GST} + + + + GST + Canadian GST Charged + 1 + 0.25 + 0.25 + + {TAX} + {CALCULATED} + + + + 3.10::1::001::Test Product 1::{USD}{GST}|0.20::1::010::Test Product 2::{GST}|0.33::1::020::Test Product 3::{GST}|0.25::1::GST::Canadian GST Charged::{TAX}{CALCULATED} + + + + diff --git a/t/40live.t b/t/40live.t new file mode 100755 index 0000000..4d59f68 --- /dev/null +++ b/t/40live.t @@ -0,0 +1,32 @@ +use Test::More; + +BEGIN { + plan skip_all => 'MERCHANT_ID environment variable not set' + unless defined $ENV{MERCHANT_ID}; +}; + +BEGIN { plan tests => 1 + 2 }; + +BEGIN { use_ok('Business::OnlinePayment') }; + + +my $txn = new Business::OnlinePayment 'InternetSecure', + merchant_id => $ENV{MERCHANT_ID}; + +$txn->test_transaction(1); + +$txn->content( + action => 'Normal Authorization', + type => 'Visa', + card_number => '0000000000000000', + exp_date => '2004/07', + name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re", + amount => 0.01, + ); + +$txn->submit; + +is($txn->result_code, '2000', 'is result_code 2000?'); +is($txn->cardholder, "Fr\x{e9}d\x{e9}ric Bri\x{e8}re", + 'is cardholder encoded properly?'); +