version 0.01
authorMark Wells <mark@freeside.biz>
Thu, 10 Jul 2014 21:13:12 +0000 (14:13 -0700)
committerMark Wells <mark@freeside.biz>
Thu, 10 Jul 2014 21:13:12 +0000 (14:13 -0700)
.gitignore [new file with mode: 0644]
Changes
lib/Business/OnlinePayment/FirstDataGlobalGateway.pm
t/transaction.t
t/transaction_decline.t

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9788afa
--- /dev/null
@@ -0,0 +1,6 @@
+blib/
+*.sw?
+Makefile
+Makefile.old
+MYMETA.yml
+pm_to_blib
diff --git a/Changes b/Changes
index e767bd2..9ec9d88 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,5 @@
-
 Revision history for Perl module Business::OnlinePayment::FirstDataGlobalGateway
 
-0.01  unreleased
-        - original version.
+0.01    Jul 10 2014
+        Initial release.
 
index 081100d..92984da 100644 (file)
@@ -5,7 +5,7 @@ use warnings;
 use strict;
 use Data::Dumper;
 use Business::CreditCard;
-use SOAP::Lite +trace => 'all';
+use SOAP::Lite; #+trace => 'all';
 #SOAP::Lite->import(+trace=>'debug');
 
 our $VERSION = '0.01';
@@ -13,6 +13,23 @@ $VERSION = eval $VERSION; # modperlstyle: convert the string into a number
 
 our @alpha = ( 'a'..'z', 'A'..'Z', '0'..'9' );
 
+our %failure_status = (
+  302 => 'nsf',
+  501 => 'pickup',
+  502 => 'stolen',
+  503 => 'stolen', # "Fraud/Security violation"
+  504 => 'blacklisted',
+  509 => 'nsf',
+  510 => 'nsf',
+  519 => 'blacklisted',
+  521 => 'nsf',
+  522 => 'expired',
+  530 => 'blacklisted',
+  534 => 'blacklisted',
+  # others are all "declined"
+);
+  
+
 sub _info {
   {
     'info_compat'       => '0.01',
@@ -20,11 +37,14 @@ sub _info {
     '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,
+    #'token_support'     => 1, # "Transarmor" is this, but not implemented yet
+    'test_transaction'  => 1,
 
     'supported_actions' => [ 'Normal Authorization',
-                             #'Credit',
+                             'Authorization Only',
+                             'Post Authorization',
+                             'Credit',
+                             'Void',
                            ],
   };
 }
@@ -33,12 +53,9 @@ 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 ));
-
+    $self->build_subs(qw( order_number avs_code cvv2_response
+                          authorization failure_status result_code
+                          ));
 }
 
 sub map_fields {
@@ -81,6 +98,9 @@ sub map_fields {
 
     $content{'action'} = $actions{$action} || $action;
 
+    # make sure there's a combined name
+    $content{name} ||= $content{first_name} . ' ' . $content{last_name};
+
     # stuff it back into %content
     $self->content(%content);
 
@@ -105,7 +125,7 @@ sub submit {
         'login'             => 'ExactID',
         'password'          => 'Password',
 
-        'action'            => 'TransactionType',
+        'action'            => 'Transaction_Type',
 
         'amount'            => 'DollarAmount',
         'currency'          => 'Currency',
@@ -131,14 +151,6 @@ sub submit {
 
   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';
@@ -170,55 +182,63 @@ sub submit {
   }
 
   my $proxy = "$base_uri/v11";
-  my $uri = "$base_uri/rpc-enc";
 
-  my %transaction = map { $_ => $content{$_} } (qw(
+  my @transaction = map { SOAP::Data->name($_)->value( $content{$_} ) }
+  grep { defined($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
+    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
+  my $wsdl = "$proxy/wsdl";
+  my $client = SOAP::Lite->service($wsdl)->proxy($proxy)->readable(1);
+  my $action_prefix = 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc';
+  my $type_prefix = $action_prefix . '/encodedTypes';
+  $client->on_action( sub { $action_prefix . '/' . $_[1] } );
+  my $source = SOAP::Data->name('SendAndCommitSource')
+                         ->value(\@transaction)
+                         ->type("$type_prefix:Transaction");
+  local $@;
+  my $som = eval { $client->call('SendAndCommit', $source) };
+  die $@ if $@;
+  if ($som->fault) { # indicates a protocol error
+    die $som->faultstring;
+  }
 
+  $DB::single = 1;
+  $som->match('/Envelope/Body/SendAndCommitResponse/SendAndCommitResult');
+  my $result = $som->valueof; # hashref of the result properties
+  $self->is_success( $result->{Transaction_Approved} );
+  $self->authorization( $result->{Authorization_Num} );
+  $self->order_number( $result->{SequenceNo} );
+  $self->avs_code( $result->{AVS} );
+  $self->cvv2_response( $result->{CVV2} );
+
+  if (!$self->is_success) {
+    # note spelling of "EXact_Resp_Code"
+    if ($result->{EXact_Resp_Code} ne '00') {
+      # then there's something wrong with the transaction inputs
+      # (invalid card number, malformed amount, attempt to refund a 
+      # transaction that didn't happen, etc.)
+      $self->error_message($result->{EXact_Message});
+      $self->result_code($result->{EXact_Resp_Code});
+      $self->failure_status('');
+      # not a decline, as the transaction was never really detected
+    } else {
+      $self->error_message($result->{Bank_Message});
+      $self->result_code($result->{Bank_Resp_Code});
+      $self->failure_status(
+        $failure_status{$result->{Bank_Resp_Code}} || 'declined'
+      );
+    }
+  }
 }
 
 1;
index 28db227..affe60c 100644 (file)
@@ -3,37 +3,64 @@
 use strict;
 use warnings;
 use POSIX qw(strftime);
-use Test::More tests => 1;
-
+use Test::More;
 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 $login = $ENV{BOP_TEST_LOGIN};
+my $password = $ENV{BOP_TEST_PASSWORD};
+if (!$login) {
+  plan skip_all => "no test credentials provided; set BOP_TEST_LOGIN and BOP_TEST_PASSWORD to test communication with the gateway.",
+  1;
+  exit(0);
+}
+
+plan tests => 2;
+
+###
+# Purchase
+###
+my %content = (
+  login    => $login,
+  password => $password,
+  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            => '94804',
+);
 
 my $tx = new Business::OnlinePayment( 'FirstDataGlobalGateway' );
 
-$tx->content( %content );
+$tx->content( %content,
+              action => 'Normal Authorization' );
 
 $tx->test_transaction(1);
 
 $tx->submit;
 
-is( $tx->is_success, 1, 'Test transaction successful')
-  or diag('iATS Payments error: '. $tx->error_message);
-            
+is( $tx->is_success, 1, 'purchase' )
+  or diag('Gateway error: '. $tx->error_message);
+
+###
+# Refund
+###
+my $auth = $tx->authorization;
+$tx = new Business::OnlinePayment( 'FirstDataGlobalGateway' );
+$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;
index 47a4cd1..a8efafb 100644 (file)
@@ -3,25 +3,35 @@
 use strict;
 use warnings;
 use POSIX qw(strftime);
-use Test::More tests => 3;
-
+use Test::More;
 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 $login = $ENV{BOP_TEST_LOGIN};
+my $password = $ENV{BOP_TEST_PASSWORD};
+if (!$login) {
+  plan skip_all => "no test credentials provided; set BOP_TEST_LOGIN and BOP_TEST_PASSWORD to test communication with the gateway.",
+  1;
+  exit(0);
+}
+
+plan tests => 2;
+my %content = (
+  login    => $login,
+  password => $password,
+  action         => "Normal Authorization",
+  type           => "CC",
+  description    => "Business::OnlinePayment::FirstDataGlobalGateway test",
+  card_number    => '4111111111111111',
+  cvv2           => '123',
+  expiration     => '12/20',
+  amount         => '5521.00', # trigger error 521
+  first_name     => 'Tofu',
+  last_name      => 'Beast',
+  address        => '1234 Soybean Ln.',
+  city           => 'Soyville',
+  state          => 'CA', #where else?
+  zip            => '54545',
+);
 
 my $tx = new Business::OnlinePayment( 'FirstDataGlobalGateway' );
 
@@ -31,8 +41,9 @@ $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');
+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;