summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2014-07-09 12:13:23 -0700
committerIvan Kohler <ivan@freeside.biz>2014-07-09 12:13:23 -0700
commitf6660ccc1cf3719650dc179aa7b1fd117b07ed0b (patch)
tree76a750ca7ebe6fee19507681060e4642f947514b
initial try at a module
-rw-r--r--Changes6
-rw-r--r--MANIFEST13
-rw-r--r--Makefile.PL23
-rw-r--r--README14
-rw-r--r--ignore.txt12
-rw-r--r--lib/Business/OnlinePayment/FirstDataGlobalGateway.pm319
-rw-r--r--t/00-load.t10
-rw-r--r--t/boilerplate.t49
-rw-r--r--t/manifest.t13
-rw-r--r--t/pod-coverage.t18
-rw-r--r--t/pod.t12
-rw-r--r--t/transaction.t39
-rw-r--r--t/transaction_decline.t38
13 files changed, 566 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..e767bd2
--- /dev/null
+++ b/Changes
@@ -0,0 +1,6 @@
+
+Revision history for Perl module Business::OnlinePayment::FirstDataGlobalGateway
+
+0.01 unreleased
+ - original version.
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..b7b925c
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,13 @@
+Changes
+MANIFEST
+Makefile.PL
+README
+ignore.txt
+lib/Business/OnlinePayment/FirstDataGlobalGateway.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
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..838e74d
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,23 @@
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'Business::OnlinePayment::FirstDataGlobalGateway',
+ AUTHOR => q{Ivan Kohler <ivan-firstdataglobalgateway@420.am>},
+ VERSION_FROM => 'lib/Business/OnlinePayment/FirstDataGlobalGateway.pm',
+ ABSTRACT_FROM => 'lib/Business/OnlinePayment/FirstDataGlobalGateway.pm',
+ ($ExtUtils::MakeMaker::VERSION >= 6.3002
+ ? ('LICENSE'=> 'perl')
+ : ()),
+ PL_FILES => {},
+ PREREQ_PM => {
+ 'Test::More' => 0,
+ 'Business::OnlinePayment' => 3.01,
+ 'SOAP::Lite' => 0,
+ 'Data::Dumper' => 0,
+ },
+ dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+ clean => { FILES => 'Business-OnlinePayment-FirstDataGlobalGateway-*' },
+);
+
diff --git a/README b/README
new file mode 100644
index 0000000..27e01eb
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
+Copyright (c) 2014 Freeside Internet Services, Inc.
+All rights reserved. This program is free software; you can redistribute it
+and/or modify it under the same terms as Perl itself.
+
+This is Business::OnlinePayment::FirstDataGlobalGateway, a
+Business::OnlinePayment backend module for First Data Global Gateway e4. It is
+only useful if you have a merchant account with First Data:
+https://www.firstdata.com/en_us/products/merchants/ecommerce/online-payment-processing.html
+
+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/ignore.txt b/ignore.txt
new file mode 100644
index 0000000..c1fd48f
--- /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-FirstDataGlobalGateway-*
diff --git a/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm b/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm
new file mode 100644
index 0000000..081100d
--- /dev/null
+++ b/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm
@@ -0,0 +1,319 @@
+package Business::OnlinePayment::FirstDataGlobalGateway;
+use base qw( Business::OnlinePayment );
+
+use warnings;
+use strict;
+use Data::Dumper;
+use Business::CreditCard;
+use SOAP::Lite +trace => 'all';
+#SOAP::Lite->import(+trace=>'debug');
+
+our $VERSION = '0.01';
+$VERSION = eval $VERSION; # modperlstyle: convert the string into a number
+
+our @alpha = ( 'a'..'z', 'A'..'Z', '0'..'9' );
+
+sub _info {
+ {
+ 'info_compat' => '0.01',
+ 'gateway_name' => 'First Data Global Gateway e4',
+ 'gateway_url' => 'https://www.firstdata.com/en_us/products/merchants/ecommerce/online-payment-processing.html',
+ 'module_version' => $VERSION,
+ 'supported_types' => [ 'CC' ], #, 'ECHECK' ],
+ #'token_support' => 1,
+ #'test_transaction' => 1,
+
+ 'supported_actions' => [ 'Normal Authorization',
+ #'Credit',
+ ],
+ };
+}
+
+sub set_defaults {
+ my $self = shift;
+ #my %opts = @_;
+
+ #$self->build_subs(qw( order_number avs_code cvv2_response
+ # response_page response_code response_headers
+ # ));
+
+ $self->build_subs(qw( avs_code ));
+
+}
+
+sub map_fields {
+ my($self) = @_;
+
+ my %content = $self->content();
+
+ # TYPE MAP
+ my %types = ( 'visa' => 'CC',
+ 'mastercard' => 'CC',
+ 'american express' => 'CC',
+ 'discover' => 'CC',
+ 'check' => 'ECHECK',
+ );
+ $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
+ $self->transaction_type($content{'type'});
+
+ # ACTION MAP
+ my $action = lc($content{'action'});
+ my %actions =
+ ( 'normal authorization' => '00', # Purchase
+ 'authorization_only' => '01', #
+ 'post authorization' => '02', # Pre-Authorization Completion
+ # '' => '03', # Forced Post
+ 'credit' => '04', # Refund
+ # '' => '05', # Pre-Authorization Only
+ 'void' => '13', # Void
+ #'reverse authorization' => '',
+
+ # '' => '07', # PayPal Order
+ # '' => '32', # Tagged Pre-Authorization Completion
+ # '' => '33', # Tagged Void
+ # '' => '34', # Tagged Refund
+ # '' => '83', # CashOut (ValueLink, v9 or higher end point only)
+ # '' => '85', # Activation (ValueLink, v9 or higher end point only)
+ # '' => '86', # Balance Inquiry (ValueLink, v9 or higher end point only)
+ # '' => '88', # Reload (ValueLink, v9 or higher end point only)
+ # '' => '89', # Deactivation (ValueLink, v9 or higher end point only)
+ );
+
+ $content{'action'} = $actions{$action} || $action;
+
+ # stuff it back into %content
+ $self->content(%content);
+
+}
+
+sub remap_fields {
+ my($self,%map) = @_;
+
+ my %content = $self->content();
+ foreach(keys %map) {
+ $content{$map{$_}} = $content{$_};
+ }
+ $self->content(%content);
+}
+
+sub submit {
+ my($self) = @_;
+
+ $self->map_fields;
+
+ $self->remap_fields(
+ 'login' => 'ExactID',
+ 'password' => 'Password',
+
+ 'action' => 'TransactionType',
+
+ 'amount' => 'DollarAmount',
+ 'currency' => 'Currency',
+ 'card_number' => 'Card_Number',
+ 'track1' => 'Track1',
+ 'track2' => 'Track2',
+ 'expiration' => 'Expiry_Date',
+ 'name' => 'CardHoldersName',
+ 'cvv2' => 'VerificationStr2',
+
+ 'authorization' => 'Authorization_Num',
+ 'order_number' => 'Reference_No',
+
+ 'zip' => 'ZipCode',
+ 'tax' => 'Tax1Amount',
+ 'customer_id' => 'Customer_Ref',
+ 'customer_ip' => 'Client_IP',
+ 'email' => 'Client_Email',
+
+ #account_type => 'accountType',
+
+ );
+
+ my %content = $self->content();
+
+ #$content{'mop'} = $mop{ cardtype($content{creditCardNum}) }
+ # if $content{'type'} eq 'CC';
+
+ #if ( $self->test_transaction ) {
+ # $content{agentCode} = 'TEST88';
+ # $content{password} = 'TEST88';
+ #}
+
+ $content{Expiry_Date} =~ s/\///;
+
+ $content{country} ||= 'US';
+
+ $content{VerificationStr1} =
+ join('|', map $content{$_}, qw( address zip city state country ));
+ $content{VerificationStr1} .= '|'. $content{'phone'}
+ if $content{'type'} eq 'ECHECK';
+
+ $content{CVD_Presence_Ind} = '1' if length($content{VerificationStr2});
+
+ $content{'Reference_No'} ||= join('', map $alpha[int(rand(62))], (1..20) );
+
+ #XXX this should be exposed as a standard B:OP field, not just recurring/no
+ if ( defined($content{'recurring_billing'})
+ && $content{'recurring_billing'} =~ /^[y1]/ ) {
+ $content{'Ecommerce_Flag'} = '2';
+ } else {
+ #$content{'Ecommerce_Flag'} = '1'; 7? if there's an IP?
+ }
+
+ my $base_uri;
+ if ( $self->test_transaction ) {
+ $base_uri =
+ 'https://api.demo.globalgatewaye4.firstdata.com/transaction';
+ } else {
+ $base_uri =
+ 'https://api.globalgatewaye4.firstdata.com/vplug-in/transaction';
+ }
+
+ my $proxy = "$base_uri/v11";
+ my $uri = "$base_uri/rpc-enc";
+
+ my %transaction = map { $_ => $content{$_} } (qw(
+ ExactID Password Transaction_Type DollarAmount Card_Number Transaction_Tag
+ Track1 Track2 Authorization_Num Expiry_Date CardHoldersName
+ VerificationStr1 VerificationStr2 CVD_Presence_Ind Reference_No ZipCode
+ Tax1Amount Tax1Number Tax2Amount Tax2Number Customer_Ref Reference_3
+ Language Client_IP Client_Email user_name Currency PartialRedemption
+ CAVV XID Ecommerce_Flag
+ ));
+ #TransarmorToken CardType EAN VirtualCard CardCost FraudSuspected
+ #CheckNumber CheckType BankAccountNumber BankRoutingNumber CustomerName
+ #CustomerIDType CustomerID
+
+ #my @opts = map { SOAP::Data->name($_)->value( $data{$_} ) }
+ # keys %data;
+
+ my $result = SOAP::Lite
+ ->proxy($proxy)
+
+ ->default_ns($base_uri)
+ ->uri($uri)
+
+ ->on_action( sub { join '/', @_ } )
+ #->on_action( sub { join '', @_ } )
+ #->on_action(sub { qq("$_[0]") }) #? https://firstdata.zendesk.com/entries/407569-First-Data-Global-Gateway-e4-Web-Service-API-Sample-Code-Perl
+ ->autotype(0)
+
+ ->readable(1)
+
+ ->ns($uri,'q1')
+ ->SendAndCommit( SOAP::Data->name('Transaction')->value( \%transaction ) )
+
+ ->result();
+
+ die Dumper($result);
+
+ die Dumper($result->result) if $result->fault;
+ #die $result->fault->faultstring if $result->fault;
+
+ die Dumper($result);
+
+ #$self->is_success
+ #$self->authorization
+ #$self->avs_code
+ #$self->error_message
+ #$self->result_code
+ ##$self->failure_status
+
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Business::OnlinePayment::FirstDataGlobalGateway - First Data Global Gateway e4 backend for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+ use Business::OnlinePayment;
+
+ my $tx =
+ new Business::OnlinePayment( 'FirstDataGlobalGateway' );
+
+ $tx->content(
+ login => 'TEST88', # ExactID
+ password => 'TEST88', #password
+
+ type => 'CC',
+ action => 'Normal Authorization',
+ amount => '1.00',
+
+ first_name => 'Tofu',
+ last_name => 'Beast',
+ address => '123 Anystreet',
+ city => 'Anywhere',
+ state => 'UT',
+ zip => '84058',
+
+ card_number => '4111111111111111',
+ expiration => '09/20',
+ cvv2 => '124',
+
+ #optional
+ customer_ip => '1.2.3.4',
+ );
+ $tx->submit();
+
+ if($tx->is_success()) {
+ print "Card processed successfully: ".$tx->authorization."\n";
+ } else {
+ print "Card was rejected: ".$tx->error_message."\n";
+ }
+
+=head1 SUPPORTED TRANSACTION TYPES
+
+=head2 CC, Visa, MasterCard, American Express, Discover
+
+Content required: type, login, action, amount, card_number, expiration.
+
+=head2 (NOT YET) Check
+
+Content required: type, login, action, amount, name, account_number, routing_code.
+
+=head1 DESCRIPTION
+
+For detailed information see L<Business::OnlinePayment>.
+
+=head1 METHODS AND FUNCTIONS
+
+See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.
+
+=head2 result_code
+
+Returns the response error code.
+
+=head2 error_message
+
+Returns the response error number.
+
+=head2 action
+
+The following actions are valid
+
+ Normal Authorization
+ Authorization Only
+ Post Authorization
+ Credit
+ Void
+
+=head1 COMPATIBILITY
+
+Business::OnlinePayment::FirstDataGlobalGateway uses the v11 version of the API
+at this time.
+
+=head1 AUTHORS
+
+Ivan Kohler <ivan-firstdataglobalgateway@freeside.biz>
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlinePayment>.
+
+=cut
+
diff --git a/t/00-load.t b/t/00-load.t
new file mode 100644
index 0000000..1a0cc65
--- /dev/null
+++ b/t/00-load.t
@@ -0,0 +1,10 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'Business::OnlinePayment::FirstDataGlobalGateway' ) || print "Bail out!
+";
+}
+
+diag( "Testing Business::OnlinePayment::FirstDataGlobalGateway $Business::OnlinePayment::FirstDataGlobalGateway::VERSION, Perl $], $^X" );
diff --git a/t/boilerplate.t b/t/boilerplate.t
new file mode 100644
index 0000000..7a269c7
--- /dev/null
+++ b/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/FirstDataGlobalGateway.pm');
+
diff --git a/t/manifest.t b/t/manifest.t
new file mode 100644
index 0000000..45eb83f
--- /dev/null
+++ b/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/pod-coverage.t b/t/pod-coverage.t
new file mode 100644
index 0000000..c021dd4
--- /dev/null
+++ b/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/pod.t b/t/pod.t
new file mode 100644
index 0000000..ee8b18a
--- /dev/null
+++ b/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/transaction.t b/t/transaction.t
new file mode 100644
index 0000000..28db227
--- /dev/null
+++ b/t/transaction.t
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More tests => 1;
+
+use Business::OnlinePayment;
+
+my %content = (
+ login => '124freeside',
+ password => 'freeside124',
+ action => "Normal Authorization",
+ type => "CC",
+ description => "Business::OnlinePayment::FirstDataGlobalGateway test",
+ card_number => '4111111111111111',
+ cvv2 => '123',
+ expiration => '12/20',
+ amount => '1.00',
+ first_name => 'Tofu',
+ last_name => 'Beast',
+ address => '1234 Soybean Ln.',
+ city => 'Soyville',
+ state => 'CA', #where else?
+ zip => '54545',
+);
+
+my $tx = new Business::OnlinePayment( 'FirstDataGlobalGateway' );
+
+$tx->content( %content );
+
+$tx->test_transaction(1);
+
+$tx->submit;
+
+is( $tx->is_success, 1, 'Test transaction successful')
+ or diag('iATS Payments error: '. $tx->error_message);
+
+1;
diff --git a/t/transaction_decline.t b/t/transaction_decline.t
new file mode 100644
index 0000000..47a4cd1
--- /dev/null
+++ b/t/transaction_decline.t
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More tests => 3;
+
+use Business::OnlinePayment;
+
+my %content = (
+ action => "Normal Authorization",
+ type => "CC",
+ description => "Business::OnlinePayment::FirstDataGlobalGateway test",
+ card_number => '4111111111111111',
+ cvv2 => '123',
+ expiration => '12/20',
+ amount => '2.00',
+ first_name => 'Tofu',
+ last_name => 'Beast',
+ address => '1234 Soybean Ln.',
+ city => 'Soyville',
+ state => 'CA', #where else?
+ zip => '54545',
+);
+
+my $tx = new Business::OnlinePayment( 'FirstDataGlobalGateway' );
+
+$tx->content( %content );
+
+$tx->test_transaction(1);
+
+$tx->submit;
+
+unlike( $tx->error_message, qr/^Agent code has not been set up/, 'Test decline not a login error');
+is( $tx->is_success, 0, 'Test decline transaction successful');
+is( $tx->failure_status, 'decline', 'Test decline failure_status set');
+
+1;