initial try at a module
authorIvan Kohler <ivan@freeside.biz>
Wed, 9 Jul 2014 19:13:23 +0000 (12:13 -0700)
committerIvan Kohler <ivan@freeside.biz>
Wed, 9 Jul 2014 19:13:23 +0000 (12:13 -0700)
13 files changed:
Changes [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
README [new file with mode: 0644]
ignore.txt [new file with mode: 0644]
lib/Business/OnlinePayment/FirstDataGlobalGateway.pm [new file with mode: 0644]
t/00-load.t [new file with mode: 0644]
t/boilerplate.t [new file with mode: 0644]
t/manifest.t [new file with mode: 0644]
t/pod-coverage.t [new file with mode: 0644]
t/pod.t [new file with mode: 0644]
t/transaction.t [new file with mode: 0644]
t/transaction_decline.t [new file with mode: 0644]

diff --git a/Changes b/Changes
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..838e74d
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..c1fd48f
--- /dev/null
@@ -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 (file)
index 0000000..081100d
--- /dev/null
@@ -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 (file)
index 0000000..1a0cc65
--- /dev/null
@@ -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 (file)
index 0000000..7a269c7
--- /dev/null
@@ -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 (file)
index 0000000..45eb83f
--- /dev/null
@@ -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 (file)
index 0000000..c021dd4
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..28db227
--- /dev/null
@@ -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 (file)
index 0000000..47a4cd1
--- /dev/null
@@ -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;