From 094794d04a28d126f72f89359284d75750798409 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 10 Jul 2014 14:13:12 -0700 Subject: [PATCH] version 0.01 --- .gitignore | 6 + Changes | 5 +- .../OnlinePayment/FirstDataGlobalGateway.pm | 132 ++++++++++++--------- t/transaction.t | 73 ++++++++---- t/transaction_decline.t | 51 ++++---- 5 files changed, 165 insertions(+), 102 deletions(-) create mode 100644 .gitignore 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 index e767bd2..9ec9d88 100644 --- 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. diff --git a/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm b/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm index 081100d..92984da 100644 --- a/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm +++ b/lib/Business/OnlinePayment/FirstDataGlobalGateway.pm @@ -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; diff --git a/t/transaction.t b/t/transaction.t index 28db227..affe60c 100644 --- a/t/transaction.t +++ b/t/transaction.t @@ -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; diff --git a/t/transaction_decline.t b/t/transaction_decline.t index 47a4cd1..a8efafb 100644 --- a/t/transaction_decline.t +++ b/t/transaction_decline.t @@ -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; -- 2.11.0