From 40ddda0cf0f1db88338fdc12be52d996ebc5f6ee Mon Sep 17 00:00:00 2001
From: Johan Herrera
Date: Mon, 16 Jan 2023 12:02:46 -0500
Subject: [PATCH 001/390] Credorax: Add support for Network Tokens
Summary:
------------------------------
Enable the Credorax gateway to support payments via network_token
Closes #4679
Remote Test:
------------------------------
51 tests, 163 assertions, 12 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
76.4706% passed
tests failures not related with the changes
Unit Tests:
------------------------------
26 tests, 132 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
756 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/credorax.rb | 11 +++++-
test/remote/gateways/remote_credorax_test.rb | 15 +++++++-
test/unit/gateways/credorax_test.rb | 37 +++++++++++++++++++
4 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index fdbf33a2c50..c0aab1a6b08 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -55,6 +55,7 @@
* Credorax: Support google pay and apple pay [edgarv09] #4661
* Plexo: Add support for 5 new credit card brands (passcard, edenred, anda, tarjeta-d, sodexo) [edgarv09] #4652
* Authorize.net: Google pay token support [sainterman] #4659
+* Credorax: Add support for Network Tokens [jherreraa] #4679
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb
index 768f47cadc2..0598cbea856 100644
--- a/lib/active_merchant/billing/gateways/credorax.rb
+++ b/lib/active_merchant/billing/gateways/credorax.rb
@@ -30,7 +30,8 @@ class CredoraxGateway < Gateway
NETWORK_TOKENIZATION_CARD_SOURCE = {
'apple_pay' => 'applepay',
- 'google_pay' => 'googlepay'
+ 'google_pay' => 'googlepay',
+ 'network_token' => 'vts_mdes_token'
}
RESPONSE_MESSAGES = {
@@ -284,7 +285,7 @@ def add_invoice(post, money, options)
def add_payment_method(post, payment_method)
post[:c1] = payment_method&.name || ''
- post[:b21] = NETWORK_TOKENIZATION_CARD_SOURCE[payment_method.source.to_s] if payment_method.is_a? NetworkTokenizationCreditCard
+ add_network_tokenization_card(post, payment_method) if payment_method.is_a? NetworkTokenizationCreditCard
post[:b2] = CARD_TYPES[payment_method.brand] || ''
post[:b1] = payment_method.number
post[:b5] = payment_method.verification_value
@@ -292,6 +293,12 @@ def add_payment_method(post, payment_method)
post[:b3] = format(payment_method.month, :two_digits)
end
+ def add_network_tokenization_card(post, payment_method)
+ post[:b21] = NETWORK_TOKENIZATION_CARD_SOURCE[payment_method.source.to_s]
+ post[:token_eci] = payment_method&.eci if payment_method.source.to_s == 'network_token'
+ post[:token_crypto] = payment_method&.payment_cryptogram if payment_method.source.to_s == 'network_token'
+ end
+
def add_stored_credential(post, options)
add_transaction_type(post, options)
# if :transaction_type option is not passed, then check for :stored_credential options
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
index e7973e90c10..cc5842d709e 100644
--- a/test/remote/gateways/remote_credorax_test.rb
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -62,7 +62,13 @@ def setup
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '07')
+
+ @nt_credit_card = network_tokenization_credit_card('4176661000001015',
+ brand: 'visa',
+ eci: '07',
+ source: :network_token,
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA=')
end
def test_successful_purchase_with_apple_pay
@@ -79,6 +85,13 @@ def test_successful_purchase_with_google_pay
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_network_token
+ response = @gateway.purchase(@amount, @nt_credit_card, @options)
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
def test_transcript_scrubbing_network_tokenization_card
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @apple_pay_card, @options)
diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb
index 76ccef0cab4..c6386f4c022 100644
--- a/test/unit/gateways/credorax_test.rb
+++ b/test/unit/gateways/credorax_test.rb
@@ -42,6 +42,23 @@ def setup
}
}
}
+
+ @nt_credit_card = network_tokenization_credit_card('4176661000001015',
+ brand: 'visa',
+ eci: '07',
+ source: :network_token,
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA=')
+
+ @apple_pay_card = network_tokenization_credit_card('4176661000001015',
+ month: 10,
+ year: Time.new.year + 2,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ eci: '07',
+ transaction_id: 'abc123',
+ source: :apple_pay)
end
def test_supported_card_types
@@ -1049,6 +1066,26 @@ def test_3ds_2_optional_fields_does_not_empty_fields
assert_equal post, {}
end
+ def test_successful_purchase_with_network_token
+ response = stub_comms do
+ @gateway.purchase(@amount, @nt_credit_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/b21=vts_mdes_token&token_eci=07&token_crypto=AgAAAAAAosVKVV7FplLgQRYAAAA%3D/, data)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_other_than_network_token
+ response = stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/b21=applepay/, data)
+ assert_not_match(/token_eci=/, data)
+ assert_not_match(/token_crypto=/, data)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
private
def stored_credential_options(*args, id: nil)
From ac06be76c25fa3a1ca81a67a56430bc6e940b176 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Wed, 18 Jan 2023 17:02:06 -0500
Subject: [PATCH 002/390] Stripe PI: use MultiResponse in create_setup_intent
This change makes it possible to access the result of the call to /payment_methods alongside the ultimate result of the call to /setup_intents
CER-357
REMOTE
80 tests, 378 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
UNIT
5439 tests, 77065 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
756 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 39 +++++++++++--------
.../remote_stripe_payment_intents_test.rb | 7 ++++
3 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c0aab1a6b08..6ad71bde438 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -56,6 +56,7 @@
* Plexo: Add support for 5 new credit card brands (passcard, edenred, anda, tarjeta-d, sodexo) [edgarv09] #4652
* Authorize.net: Google pay token support [sainterman] #4659
* Credorax: Add support for Network Tokens [jherreraa] #4679
+* Stripe PI: use MultiResponse in create_setup_intent [jcreiff] #4683
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 7135cf7d2a0..989078dd9a8 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -117,20 +117,24 @@ def update_intent(money, intent_id, payment_method, options = {})
end
def create_setup_intent(payment_method, options = {})
- post = {}
- add_customer(post, options)
- result = add_payment_method_token(post, payment_method, options)
- return result if result.is_a?(ActiveMerchant::Billing::Response)
+ MultiResponse.run do |r|
+ r.process do
+ post = {}
+ add_customer(post, options)
+ result = add_payment_method_token(post, payment_method, options, r)
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
- add_metadata(post, options)
- add_return_url(post, options)
- add_fulfillment_date(post, options)
- request_three_d_secure(post, options)
- post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
- post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
- post[:description] = options[:description] if options[:description]
+ add_metadata(post, options)
+ add_return_url(post, options)
+ add_fulfillment_date(post, options)
+ request_three_d_secure(post, options)
+ post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
+ post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
+ post[:description] = options[:description] if options[:description]
- commit(:post, 'setup_intents', post, options)
+ commit(:post, 'setup_intents', post, options)
+ end
+ end
end
def retrieve_setup_intent(setup_intent_id, options = {})
@@ -305,7 +309,7 @@ def add_return_url(post, options)
post[:return_url] = options[:return_url] if options[:return_url]
end
- def add_payment_method_token(post, payment_method, options)
+ def add_payment_method_token(post, payment_method, options, responses = [])
case payment_method
when StripePaymentToken
post[:payment_method_data] = {
@@ -318,7 +322,7 @@ def add_payment_method_token(post, payment_method, options)
when String
extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
when ActiveMerchant::Billing::CreditCard
- get_payment_method_data_from_card(post, payment_method, options)
+ get_payment_method_data_from_card(post, payment_method, options, responses)
end
end
@@ -358,16 +362,17 @@ def tokenize_apple_google(payment, options = {})
end
end
- def get_payment_method_data_from_card(post, payment_method, options)
- return create_payment_method_and_extract_token(post, payment_method, options) unless off_session_request?(options)
+ def get_payment_method_data_from_card(post, payment_method, options, responses)
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options)
post[:payment_method_data] = add_payment_method_data(payment_method, options)
end
- def create_payment_method_and_extract_token(post, payment_method, options)
+ def create_payment_method_and_extract_token(post, payment_method, options, responses)
payment_method_response = create_payment_method(payment_method, options)
return payment_method_response if payment_method_response.failure?
+ responses << payment_method_response
add_payment_method_token(post, payment_method_response.params['id'], options)
end
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 932ed7bc85b..01625e4cc83 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -1204,6 +1204,13 @@ def test_failed_verify
assert_equal 'Your card was declined.', verify.message
end
+ def test_verify_stores_response_for_payment_method_creation
+ assert verify = @gateway.verify(@visa_card)
+
+ assert_equal 2, verify.responses.count
+ assert_match 'pm_', verify.responses.first.params['id']
+ end
+
def test_moto_enabled_card_requires_action_when_not_marked
options = {
currency: 'GBP',
From f6e3f6c53a1cf8873731aa7fa69171195a2339b7 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Fri, 20 Jan 2023 11:10:10 -0500
Subject: [PATCH 003/390] Payeezy change `method` on capture (#4684)
For Apple Pay transactions, the value for `method` is set to `3DS`, but
when executing a `capture`, this value should be changed to
`credit_card`.
This differs from other use cases where the value provided on auth
transactions should be the same one given for capture.
Unit: 45 tests, 206 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
lib/active_merchant/billing/gateways/payeezy.rb | 2 ++
test/remote/gateways/remote_payeezy_test.rb | 8 ++++++++
2 files changed, 10 insertions(+)
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index b27eb7edac2..b961ee62925 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -155,6 +155,8 @@ def amount_from_authorization(authorization)
def add_authorization_info(params, authorization, options = {})
transaction_id, transaction_tag, method, = authorization.split('|')
params[:method] = method == 'token' ? 'credit_card' : method
+ # If the previous transaction `method` value was 3DS, it needs to be set to `credit_card` on follow up transactions
+ params[:method] = 'credit_card' if method == '3DS'
if options[:reversal_id]
params[:reversal_id] = options[:reversal_id]
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
index ebe148841a8..fc9b60877e4 100644
--- a/test/remote/gateways/remote_payeezy_test.rb
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -86,6 +86,14 @@ def test_successful_purchase_with_apple_pay
assert_success response
end
+ def test_successful_authorize_and_capture_with_apple_pay
+ assert auth = @gateway.authorize(@amount, @apple_pay_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
def test_successful_purchase_with_echeck
options = @options.merge({ customer_id_type: '1', customer_id_number: '1', client_email: 'test@example.com' })
assert response = @gateway.purchase(@amount, @check, options)
From a456371fc8baef2d75fb612be10af872eee2751f Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 23 Jan 2023 10:20:29 -0500
Subject: [PATCH 004/390] Credorax: Update MIT logic
Credorax is changing the requirements for MIT transactions
in regards to NTIDs. Moving forward, all MIT transactions need to
pass in the NTID if present. Additionally the NTID value
is from the `Z50` flag and passed directly from the card scheme.
Credorax notification:
For any MIT transactions, this Trace ID must be stored from the original Card Holder initiated (CIT) transaction where the Card details were stored on file originally. When using Finaro gateway/acquiring this Trace ID is returned in the z50 parameter and must be sent in the g6 parameter for any subsequent MIT transactions, (if using an external token engine or your own token engine and the original CIT is not processed through Finaro, please liaise with your processor to obtain this Trace ID). Additionally, this original Card holder initiated transaction must be Fully authenticated with 3DSecure to comply with PSD2/SCA regulation in place.
Please make sure that you comply with this for any customers saving their card details on file for subsequent MIT processed through Finaro
ECS-2650
Test Summary
The remote tests with stored creds now work but take ~17 minutes for me. Additionally
there are some failing tests for me (on master as well). Can anyone else run these?
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/credorax.rb | 8 ++------
test/remote/gateways/remote_credorax_test.rb | 4 ++--
test/unit/gateways/credorax_test.rb | 1 +
4 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 6ad71bde438..e0f7e6f1673 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -57,6 +57,7 @@
* Authorize.net: Google pay token support [sainterman] #4659
* Credorax: Add support for Network Tokens [jherreraa] #4679
* Stripe PI: use MultiResponse in create_setup_intent [jcreiff] #4683
+* Credorax: Correct NTID logic for MIT transactions [aenand] #4686
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb
index 0598cbea856..8e4e9ee6097 100644
--- a/lib/active_merchant/billing/gateways/credorax.rb
+++ b/lib/active_merchant/billing/gateways/credorax.rb
@@ -307,20 +307,16 @@ def add_stored_credential(post, options)
if stored_credential[:initiator] == 'merchant'
case stored_credential[:reason_type]
when 'recurring'
- recurring_properties(post, stored_credential)
+ post[:a9] = stored_credential[:initial_transaction] ? '1' : '2'
when 'installment', 'unscheduled'
post[:a9] = '8'
end
+ post[:g6] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
else
post[:a9] = '9'
end
end
- def recurring_properties(post, stored_credential)
- post[:a9] = stored_credential[:initial_transaction] ? '1' : '2'
- post[:g6] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
- end
-
def add_customer_data(post, options)
post[:d1] = options[:ip] || '127.0.0.1'
if (billing_address = options[:billing_address])
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
index cc5842d709e..e545aa5382f 100644
--- a/test/remote/gateways/remote_credorax_test.rb
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -546,7 +546,7 @@ def test_purchase_using_stored_credential_installment_mit
assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
assert_success purchase
assert_equal '8', purchase.params['A9']
- assert network_transaction_id = purchase.params['Z13']
+ assert network_transaction_id = purchase.params['Z50']
used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id)
assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
@@ -570,7 +570,7 @@ def test_purchase_using_stored_credential_unscheduled_mit
assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
assert_success purchase
assert_equal '8', purchase.params['A9']
- assert network_transaction_id = purchase.params['Z13']
+ assert network_transaction_id = purchase.params['Z50']
used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id)
assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb
index c6386f4c022..2e6232c07dc 100644
--- a/test/unit/gateways/credorax_test.rb
+++ b/test/unit/gateways/credorax_test.rb
@@ -1003,6 +1003,7 @@ def test_stored_credential_unscheduled_mit_used
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(/a9=8/, data)
+ assert_match(/g6=abc123/, data)
end.respond_with(successful_authorize_response)
assert_success response
From 2d7e4096e522725b139ecc6653f5fdd44bb6ae2c Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Thu, 22 Dec 2022 17:21:22 -0500
Subject: [PATCH 005/390] Adyen: Add support for `skip_mpi_data` flag
CER-333
This change will allow for recurring payments with Apple Pay on
Adyen by eliminating mpi data hash from request after initial payment.
Unit:
104 tests, 528 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
131 tests, 440 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
90.8397% passed
Local:
5430 tests, 77037 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 6 ++--
test/remote/gateways/remote_adyen_test.rb | 35 ++++++++++++++++++-
test/unit/gateways/adyen_test.rb | 24 +++++++++++++
4 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index e0f7e6f1673..c6924c1960e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -58,6 +58,7 @@
* Credorax: Add support for Network Tokens [jherreraa] #4679
* Stripe PI: use MultiResponse in create_setup_intent [jcreiff] #4683
* Credorax: Correct NTID logic for MIT transactions [aenand] #4686
+* Adyen: Add support for `skip_mpi_data` flag [rachelkirk] #4654
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index ccc10ba6c51..f89f0e6417c 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -435,7 +435,7 @@ def add_payment(post, payment, options, action = nil)
elsif payment.is_a?(Check)
add_bank_account(post, payment, options, action)
else
- add_mpi_data_for_network_tokenization_card(post, payment) if payment.is_a?(NetworkTokenizationCreditCard)
+ add_mpi_data_for_network_tokenization_card(post, payment, options) if payment.is_a?(NetworkTokenizationCreditCard)
add_card(post, payment)
end
end
@@ -486,7 +486,9 @@ def add_reference(post, authorization, options = {})
post[:originalReference] = original_reference
end
- def add_mpi_data_for_network_tokenization_card(post, payment)
+ def add_mpi_data_for_network_tokenization_card(post, payment, options)
+ return if options[:skip_mpi_data] == 'Y'
+
post[:mpiData] = {}
post[:mpiData][:authenticationResponse] = 'Y'
post[:mpiData][:cavv] = payment.payment_cryptogram
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index b888898b8ed..d4fb596ba21 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -1161,7 +1161,7 @@ def test_invalid_expiry_month_for_purchase
card = credit_card('4242424242424242', month: 16)
assert response = @gateway.purchase(@amount, card, @options)
assert_failure response
- assert_equal 'The provided Expiry Date is not valid.: Expiry month should be between 1 and 12 inclusive', response.message
+ assert_equal 'The provided Expiry Date is not valid.: Expiry month should be between 1 and 12 inclusive: 16', response.message
end
def test_invalid_expiry_year_for_purchase
@@ -1343,6 +1343,39 @@ def test_auth_capture_refund_with_network_txn_id
assert_success refund
end
+ def test_purchase_with_skip_mpi_data
+ options = {
+ reference: '345123',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'shopper 123',
+ billing_address: address(country: 'US', state: 'CA')
+ }
+ first_options = options.merge(
+ order_id: generate_unique_id,
+ shopper_interaction: 'Ecommerce',
+ recurring_processing_model: 'Subscription'
+ )
+ assert auth = @gateway.authorize(@amount, @apple_pay_card, first_options)
+ assert_success auth
+
+ assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+
+ used_options = options.merge(
+ order_id: generate_unique_id,
+ skip_mpi_data: 'Y',
+ shopper_interaction: 'ContAuth',
+ recurring_processing_model: 'Subscription',
+ network_transaction_id: auth.network_transaction_id
+ )
+
+ assert purchase = @gateway.purchase(@amount, @apple_pay_card, used_options)
+ assert_success purchase
+ end
+
def test_successful_authorize_with_sub_merchant_data
sub_merchant_data = {
sub_merchant_id: '123451234512345',
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 388ef6830e0..516435e58ba 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -662,6 +662,30 @@ def test_stored_credential_unscheduled_mit_used
assert_success response
end
+ def test_skip_mpi_data_field_omits_mpi_hash
+ options = {
+ billing_address: address(),
+ shipping_address: address(),
+ shopper_reference: 'John Smith',
+ order_id: '1001',
+ description: 'AM test',
+ currency: 'GBP',
+ customer: '123',
+ skip_mpi_data: 'Y',
+ shopper_interaction: 'ContAuth',
+ recurring_processing_model: 'Subscription',
+ network_transaction_id: '123ABC'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"shopperInteraction":"ContAuth"/, data)
+ assert_match(/"recurringProcessingModel":"Subscription"/, data)
+ refute_includes data, 'mpiData'
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_nonfractional_currency_handling
stub_comms do
@gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY'))
From 2eb14a14ae473c4a91a357341af7784bc36e1274 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Thu, 26 Jan 2023 14:24:17 -0500
Subject: [PATCH 006/390] Add Canadian Institution Numbers
Adds two additional valid Canadian Institution Numbers: 618, 842
https://en.wikipedia.org/wiki/Routing_number_(Canada)
CER-403
5439 tests, 77066 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
756 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/check.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index c6924c1960e..3375c5950ab 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -59,6 +59,7 @@
* Stripe PI: use MultiResponse in create_setup_intent [jcreiff] #4683
* Credorax: Correct NTID logic for MIT transactions [aenand] #4686
* Adyen: Add support for `skip_mpi_data` flag [rachelkirk] #4654
+* Add Canadian Institution Numbers [jcreiff] #4687
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/check.rb b/lib/active_merchant/billing/check.rb
index ca4d0171bd7..940ab14e57b 100644
--- a/lib/active_merchant/billing/check.rb
+++ b/lib/active_merchant/billing/check.rb
@@ -20,7 +20,7 @@ class Check < Model
309 310 315 320 338 340 509 540 608 614 623 809 815 819 828 829 837 839
865 879 889 899 241 242 248 250 265 275 277 290 294 301 303 307 311 314
321 323 327 328 330 332 334 335 342 343 346 352 355 361 362 366 370 372
- 376 378 807 853 890
+ 376 378 807 853 890 618 842
)
def name
From 2044af318ad2899a682191a6c5828294b85c397a Mon Sep 17 00:00:00 2001
From: naashton
Date: Mon, 6 Feb 2023 11:20:26 -0500
Subject: [PATCH 007/390] Payeezy: Handle nil and empty values for Apple Pay
Payeezy support has indicated that passing empty or nil values in place
of the `xid` and `cavv` may result in failed transactions for AMEX based
AP tokens. They also informed us that the `eci_indicator` value should
default to `5` instead of passing a nil value, following a similar
pattern.
This PR ignores empty `payment_cryptogram` and defaults the
`eci_indicator` to `5` if that value is `nil`.
Unit: 6 tests, 211 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
lib/active_merchant/billing/gateways/payeezy.rb | 6 +++---
test/unit/gateways/payeezy_test.rb | 13 +++++++++++++
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index b961ee62925..cc1221f2cc1 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -247,13 +247,13 @@ def add_network_tokenization(params, payment_method, options)
nt_card[:card_number] = payment_method.number
nt_card[:exp_date] = format_exp_date(payment_method.month, payment_method.year)
nt_card[:cvv] = payment_method.verification_value
- nt_card[:xid] = payment_method.payment_cryptogram
- nt_card[:cavv] = payment_method.payment_cryptogram
+ nt_card[:xid] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty?
+ nt_card[:cavv] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty?
nt_card[:wallet_provider_id] = 'APPLE_PAY'
params['3DS'] = nt_card
params[:method] = '3DS'
- params[:eci_indicator] = payment_method.eci
+ params[:eci_indicator] = payment_method.eci.nil? ? '5' : payment_method.eci
end
def format_exp_date(month, year)
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index 22eab291105..edf8b815ec9 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -107,6 +107,19 @@ def test_successful_purchase_with_apple_pay
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_apple_pay_no_cryptogram
+ @apple_pay_card.payment_cryptogram = ''
+ @apple_pay_card.eci = nil
+ stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['eci_indicator'], '5'
+ assert_nil request['xid']
+ assert_nil request['cavv']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase_no_name
@apple_pay_card.first_name = nil
@apple_pay_card.last_name = nil
From 6ef70d64aac14d1ba13992360502321b823985c5 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 14 Feb 2023 14:48:55 -0600
Subject: [PATCH 008/390] Tns: update test URL
Update test URL to be only secure.uat.tnspayments.
Unit Test:
15 tests, 68 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/mastercard.rb | 9 +----
lib/active_merchant/billing/gateways/tns.rb | 7 ++--
test/unit/gateways/citrus_pay_test.rb | 20 ++---------
test/unit/gateways/tns_test.rb | 36 ++-----------------
5 files changed, 8 insertions(+), 65 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3375c5950ab..2469513cbef 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -60,6 +60,7 @@
* Credorax: Correct NTID logic for MIT transactions [aenand] #4686
* Adyen: Add support for `skip_mpi_data` flag [rachelkirk] #4654
* Add Canadian Institution Numbers [jcreiff] #4687
+* Tns: update test URL [almalee24] #4698
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/mastercard.rb b/lib/active_merchant/billing/gateways/mastercard.rb
index be18bda516f..ffbb2e3fb67 100644
--- a/lib/active_merchant/billing/gateways/mastercard.rb
+++ b/lib/active_merchant/billing/gateways/mastercard.rb
@@ -218,14 +218,7 @@ def build_url(orderid, transactionid)
def base_url
if test?
- case @options[:region]
- when 'asia_pacific'
- test_ap_url
- when 'europe'
- test_eu_url
- when 'north_america', nil
- test_na_url
- end
+ test_url
else
case @options[:region]
when 'asia_pacific'
diff --git a/lib/active_merchant/billing/gateways/tns.rb b/lib/active_merchant/billing/gateways/tns.rb
index 15c47eadb82..b84da916ef5 100644
--- a/lib/active_merchant/billing/gateways/tns.rb
+++ b/lib/active_merchant/billing/gateways/tns.rb
@@ -8,13 +8,10 @@ class TnsGateway < Gateway
VERSION = '52'
self.live_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
-
self.live_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
-
self.live_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
+
+ self.test_url = "https://secure.uat.tnspayments.com/api/rest/version/#{VERSION}/"
self.display_name = 'TNS'
self.homepage_url = 'http://www.tnsi.com/'
diff --git a/test/unit/gateways/citrus_pay_test.rb b/test/unit/gateways/citrus_pay_test.rb
index 7c96c1c1308..bc39c2034bd 100644
--- a/test/unit/gateways/citrus_pay_test.rb
+++ b/test/unit/gateways/citrus_pay_test.rb
@@ -157,7 +157,7 @@ def test_unsuccessful_verify
assert_equal 'FAILURE - DECLINED', response.message
end
- def test_north_america_region_url
+ def test_url
@gateway = TnsGateway.new(
userid: 'userid',
password: 'password',
@@ -167,23 +167,7 @@ def test_north_america_region_url
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/secure.na.tnspayments.com/, endpoint)
- end.respond_with(successful_capture_response)
-
- assert_success response
- end
-
- def test_asia_pacific_region_url
- @gateway = TnsGateway.new(
- userid: 'userid',
- password: 'password',
- region: 'asia_pacific'
- )
-
- response = stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@amount, @credit_card, @options)
- end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/secure.ap.tnspayments.com/, endpoint)
+ assert_match(/secure.uat.tnspayments.com/, endpoint)
end.respond_with(successful_capture_response)
assert_success response
diff --git a/test/unit/gateways/tns_test.rb b/test/unit/gateways/tns_test.rb
index df59c9a62af..d7939f10630 100644
--- a/test/unit/gateways/tns_test.rb
+++ b/test/unit/gateways/tns_test.rb
@@ -157,7 +157,7 @@ def test_unsuccessful_verify
assert_equal 'FAILURE - DECLINED', response.message
end
- def test_north_america_region_url
+ def test__url
@gateway = TnsGateway.new(
userid: 'userid',
password: 'password',
@@ -167,39 +167,7 @@ def test_north_america_region_url
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/secure.na.tnspayments.com/, endpoint)
- end.respond_with(successful_capture_response)
-
- assert_success response
- end
-
- def test_asia_pacific_region_url
- @gateway = TnsGateway.new(
- userid: 'userid',
- password: 'password',
- region: 'asia_pacific'
- )
-
- response = stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@amount, @credit_card, @options)
- end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/secure.ap.tnspayments.com/, endpoint)
- end.respond_with(successful_capture_response)
-
- assert_success response
- end
-
- def test_europe_region_url
- @gateway = TnsGateway.new(
- userid: 'userid',
- password: 'password',
- region: 'europe'
- )
-
- response = stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@amount, @credit_card, @options)
- end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/secure.eu.tnspayments.com/, endpoint)
+ assert_match(/secure.uat.tnspayments.com/, endpoint)
end.respond_with(successful_capture_response)
assert_success response
From 9339652f2f807f8a83bcc4edb33f8d3383e833c5 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 15 Feb 2023 13:38:43 -0500
Subject: [PATCH 009/390] TrustCommerce: Update `authorization_from` to handle
`store` response (#4691)
Fix Store/Unstore features
Remote tests
--------------------------------------------------------------------------
21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
85.7143% passed
Failing tests not related with changes
unit tests
--------------------------------------------------------------------------
20 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/trust_commerce.rb | 11 ++++++++---
.../gateways/remote_trust_commerce_test.rb | 17 +++++++++++++++++
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 2469513cbef..4fb1792a512 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -61,6 +61,7 @@
* Adyen: Add support for `skip_mpi_data` flag [rachelkirk] #4654
* Add Canadian Institution Numbers [jcreiff] #4687
* Tns: update test URL [almalee24] #4698
+* TrustCommerce: Update `authorization_from` to handle `store` response [jherreraa] #4691
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index 3b805e94657..bed6830d322 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -476,9 +476,14 @@ def message_from(data)
end
def authorization_from(action, data)
- authorization = data['transid']
- authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action)
- authorization
+ case action
+ when 'store'
+ data['billingid']
+ when *VOIDABLE_ACTIONS
+ "#{data['transid']}|#{action}"
+ else
+ data['transid']
+ end
end
def split_authorization(authorization)
diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb
index 78587a190c6..3772d76d947 100644
--- a/test/remote/gateways/remote_trust_commerce_test.rb
+++ b/test/remote/gateways/remote_trust_commerce_test.rb
@@ -182,6 +182,13 @@ def test_failed_store
assert_bad_data_response(response)
end
+ def test_successful_unstore
+ assert store = @gateway.store(@credit_card)
+ assert_equal 'approved', store.params['status']
+ assert response = @gateway.unstore(store.params['billingid'])
+ assert_success response
+ end
+
def test_unstore_failure
assert response = @gateway.unstore('does-not-exist')
@@ -189,6 +196,16 @@ def test_unstore_failure
assert_failure response
end
+ def test_successful_purchase_after_store
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+ assert response = @gateway.purchase(@amount, store.params['billingid'], @options)
+ assert_equal 'Y', response.avs_result['code']
+ assert_match %r{The transaction was successful}, response.message
+
+ assert_success response
+ end
+
def test_successful_recurring
assert response = @gateway.recurring(@amount, @credit_card, periodicity: :weekly)
From 68dfff0f599515b15a68d17da1ffabaa3d977a25 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 15 Feb 2023 13:57:08 -0500
Subject: [PATCH 010/390] TrustCommerce Verify feature added (#4692)
Enable verify feature on TrustCommerce Gateway
Remote tests
--------------------------------------------------------------------------
21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications 85.7143% passed
Failing tests not related with changes
unit tests
--------------------------------------------------------------------------
22 tests, 72 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications 100% passed
---
CHANGELOG | 1 +
.../billing/gateways/trust_commerce.rb | 5 +++
.../gateways/remote_trust_commerce_test.rb | 5 +++
test/unit/gateways/trust_commerce_test.rb | 33 +++++++++++++++++++
4 files changed, 44 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 4fb1792a512..dbd0de8c174 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -62,6 +62,7 @@
* Add Canadian Institution Numbers [jcreiff] #4687
* Tns: update test URL [almalee24] #4698
* TrustCommerce: Update `authorization_from` to handle `store` response [jherreraa] #4691
+* TrustCommerce: Verify feature added [jherreraa] #4692
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index bed6830d322..f7ad38c67ff 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -248,6 +248,11 @@ def void(authorization, options = {})
commit(action, parameters)
end
+ def verify(credit_card, options = {})
+ add_creditcard(options, credit_card)
+ commit('verify', options)
+ end
+
# recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
# hosted customer billing info database.
#
diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb
index 3772d76d947..502f0ed30bd 100644
--- a/test/remote/gateways/remote_trust_commerce_test.rb
+++ b/test/remote/gateways/remote_trust_commerce_test.rb
@@ -202,7 +202,12 @@ def test_successful_purchase_after_store
assert response = @gateway.purchase(@amount, store.params['billingid'], @options)
assert_equal 'Y', response.avs_result['code']
assert_match %r{The transaction was successful}, response.message
+ end
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card)
+ assert_equal 'approved', response.params['status']
+ assert_match %r{The transaction was successful}, response.message
assert_success response
end
diff --git a/test/unit/gateways/trust_commerce_test.rb b/test/unit/gateways/trust_commerce_test.rb
index fd5a6b33538..9d1782e4e51 100644
--- a/test/unit/gateways/trust_commerce_test.rb
+++ b/test/unit/gateways/trust_commerce_test.rb
@@ -167,6 +167,22 @@ def test_transcript_scrubbing_echeck
assert_equal scrubbed_echeck_transcript, @gateway.scrub(echeck_transcript)
end
+ def test_successful_verify
+ stub_comms do
+ @gateway.verify(@credit_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(%r{action=verify}, data)
+ end.respond_with(successful_verify_response)
+ end
+
+ def test_unsuccessful_verify
+ bad_credit_card = credit_card('42909090990')
+ @gateway.expects(:ssl_post).returns(unsuccessful_verify_response)
+ assert response = @gateway.verify(bad_credit_card)
+ assert_instance_of Response, response
+ assert_failure response
+ end
+
private
def successful_authorize_response
@@ -235,6 +251,23 @@ def successful_unstore_response
RESPONSE
end
+ def successful_verify_response
+ <<~RESPONSE
+ transid=039-0170402443
+ status=approved
+ avs=0
+ cvv=M
+ RESPONSE
+ end
+
+ def unsuccessful_verify_response
+ <<~RESPONSE
+ offenders=cc
+ error=badformat
+ status=baddata
+ RESPONSE
+ end
+
def transcript
<<~TRANSCRIPT
action=sale&demo=y&password=password&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=1234&exp=0916&cc=4111111111111111&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100
From 041a06cd67c9c8c492d3df4a0afbf7778e6a7373 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Wed, 15 Feb 2023 14:05:37 -0500
Subject: [PATCH 011/390] Rapyd: Add customer object to transactions (#4664)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Description
-------------------------
Rapyd Gateway send customer info on store transactions, with this commit
it will be able to send customer on authorize and purchase transaction,
when it use a US “PMT”s include the addresses object into the customer data
in order to be able to perform US transactions properly.
Unit test
-------------------------
Finished in 0.123245 seconds.
22 tests, 103 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
Finished in 100.082672 seconds.
33 tests, 92 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
0.33 tests/s, 0.92 assertions/s
Rubocop
-------------------------
756 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 11 +++--
test/remote/gateways/remote_rapyd_test.rb | 41 +++++++++++++++++++
test/unit/gateways/rapyd_test.rb | 32 ++++++++++++++-
4 files changed, 81 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index dbd0de8c174..daa386c0cec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -63,6 +63,7 @@
* Tns: update test URL [almalee24] #4698
* TrustCommerce: Update `authorization_from` to handle `store` response [jherreraa] #4691
* TrustCommerce: Verify feature added [jherreraa] #4692
+* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 6cdd07b2792..51b0fb326ce 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -11,6 +11,8 @@ class RapydGateway < Gateway
self.homepage_url = 'https://www.rapyd.net/'
self.display_name = 'Rapyd Gateway'
+ USA_PAYMENT_METHODS = %w[us_debit_discover_card us_debit_mastercard_card us_debit_visa_card us_ach_bank]
+
STANDARD_ERROR_CODE_MAPPING = {}
def initialize(options = {})
@@ -98,6 +100,7 @@ def add_reference(authorization)
def add_auth_purchase(post, money, payment, options)
add_invoice(post, money, options)
add_payment(post, payment, options)
+ add_customer_object(post, payment, options)
add_3ds(post, payment, options)
add_address(post, payment, options)
add_metadata(post, options)
@@ -211,9 +214,11 @@ def add_payment_urls(post, options)
end
def add_customer_object(post, payment, options)
- post[:name] = "#{payment.first_name} #{payment.last_name}"
- post[:phone_number] = options[:billing_address][:phone].gsub(/\D/, '') if options[:billing_address]
- post[:email] = options[:email] if options[:email]
+ post[:name] = "#{payment.first_name} #{payment.last_name}" unless payment.is_a?(String)
+ phone = options.dig(:billing_address, :phone) .gsub(/\D/, '') unless options[:billing_address].nil?
+ post[:phone_number] = phone || options.dig(:customer, :phone_number)
+ post[:email] = options[:email] || options.dig(:customer, :email)
+ post[:addresses] = options.dig(:customer, :addresses) if USA_PAYMENT_METHODS.include?(options[:pm_type])
end
def add_customer_id(post, options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 6aead2a6dc4..f2f961a7ed3 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -46,6 +46,15 @@ def setup
xid: '00000000000000000501',
eci: '02'
}
+
+ @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', phone_number: '12125559999')
+
+ @customer_object = {
+ name: 'John Doe',
+ phone_number: '1234567890',
+ email: 'est@example.com',
+ addresses: [@address_object]
+ }
end
def test_successful_purchase
@@ -54,6 +63,38 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
+ def test_successful_authorize_with_customer_object
+ @options[:customer] = @customer_object
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_customer_object
+ @options[:customer] = @customer_object
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_success_purchase_without_customer_fullname
+ @credit_card.first_name = ''
+ @credit_card.last_name = ''
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_success_purchase_without_address_object_customer
+ @options[:pm_type] = 'us_debit_discover_card'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_successful_subsequent_purchase_with_stored_credential
@options[:currency] = 'EUR'
@options[:pm_type] = 'gi_visa_card'
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index cbb12ce94d3..bab8839b14f 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -39,6 +39,15 @@ def setup
}
@ewallet_id = 'ewallet_1a867a32b47158b30a8c17d42f12f3f1'
+
+ @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', phone_number: '12125559999')
+
+ @customer_object = {
+ name: 'John Doe',
+ phone_number: '1234567890',
+ email: 'est@example.com',
+ addresses: [@address_object]
+ }
end
def test_successful_purchase
@@ -64,7 +73,7 @@ def test_successful_purchase_with_ach
end
def test_successful_purchase_with_token
- @options.merge(customer_id: 'cus_9e1b5a357b2b7f25f8dd98827fbc4f22')
+ @options[:customer_id] = 'cus_9e1b5a357b2b7f25f8dd98827fbc4f22'
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @authorization, @options)
end.check_request do |_method, _endpoint, data, _headers|
@@ -208,6 +217,27 @@ def test_successful_store_and_unstore
assert_equal customer_id, unstore.params.dig('data', 'id')
end
+ def test_failed_purchase_without_customer_object
+ @options[:pm_type] = 'us_debit_visa_card'
+ @gateway.expects(:ssl_request).returns(failed_purchase_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'ERROR_PROCESSING_CARD - [05]', response.params['status']['error_code']
+ end
+
+ def test_successful_purchase_with_customer_object
+ stub_comms(@gateway, :ssl_request) do
+ @options[:customer] = @customer_object
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ assert_match(/"name":"Jim Reynolds"/, data)
+ assert_match(/"email":"test@example.com"/, data)
+ assert_match(/"phone_number":"5555555555"/, data)
+ assert_match(/"address1":"456 My Street","address2":"Apt 1","company":"Widgets Inc","city":"Ottawa","state":"ON","zip":"K1C2N6","country":"CA"/, data)
+ end
+ end
+
def test_successful_store_with_customer_object
response = stub_comms(@gateway, :ssl_request) do
@gateway.store(@credit_card, @options)
From 186b7aba902182eaec73c3a4b8a52fd6e5754601 Mon Sep 17 00:00:00 2001
From: cristian
Date: Mon, 30 Jan 2023 15:22:16 -0500
Subject: [PATCH 012/390] CybersourceRest: Add new gateway with authorize and
purchase
Summary:
------------------------------
Adding CybersourceRest gateway with authorize and purchase
calls.
Closes #4690
GWI-474
Remote Test:
------------------------------
Finished in 3.6855 seconds.
6 tests, 17 assertions, 1 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 35.528692 seconds.
5441 tests, 77085 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../cyber_source/cyber_source_common.rb | 32 +++
.../billing/gateways/cyber_source_rest.rb | 220 +++++++++++++++
test/fixtures.yml | 6 +
.../gateways/remote_cyber_source_rest_test.rb | 82 ++++++
test/unit/gateways/cyber_source_rest_test.rb | 262 ++++++++++++++++++
6 files changed, 603 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
create mode 100644 lib/active_merchant/billing/gateways/cyber_source_rest.rb
create mode 100644 test/remote/gateways/remote_cyber_source_rest_test.rb
create mode 100644 test/unit/gateways/cyber_source_rest_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index daa386c0cec..839057d5721 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -64,6 +64,7 @@
* TrustCommerce: Update `authorization_from` to handle `store` response [jherreraa] #4691
* TrustCommerce: Verify feature added [jherreraa] #4692
* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
+* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
new file mode 100644
index 00000000000..4055a9197bc
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
@@ -0,0 +1,32 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ module CyberSourceCommon
+ def check_billing_field_value(default, submitted)
+ if submitted.nil?
+ nil
+ elsif submitted.blank?
+ default
+ else
+ submitted
+ end
+ end
+
+ def address_names(address_name, payment_method)
+ names = split_names(address_name)
+ return names if names.any?(&:present?)
+
+ [
+ payment_method&.first_name,
+ payment_method&.last_name
+ ]
+ end
+
+ def lookup_country_code(country_field)
+ return unless country_field.present?
+
+ country_code = Country.find(country_field)
+ country_code&.code(:alpha2)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
new file mode 100644
index 00000000000..a4f3b62747d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -0,0 +1,220 @@
+require 'active_merchant/billing/gateways/cyber_source/cyber_source_common'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CyberSourceRestGateway < Gateway
+ include ActiveMerchant::Billing::CyberSourceCommon
+
+ self.test_url = 'https://apitest.cybersource.com'
+ self.live_url = 'https://api.cybersource.com'
+
+ self.supported_countries = ActiveMerchant::Billing::CyberSourceGateway.supported_countries
+ self.default_currency = 'USD'
+ self.currencies_without_fractions = ActiveMerchant::Billing::CyberSourceGateway.currencies_without_fractions
+
+ self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada]
+
+ self.homepage_url = 'http://www.cybersource.com'
+ self.display_name = 'Cybersource REST'
+
+ CREDIT_CARD_CODES = {
+ american_express: '003',
+ cartes_bancaires: '036',
+ dankort: '034',
+ diners_club: '005',
+ discover: '004',
+ elo: '054',
+ jcb: '007',
+ maestro: '042',
+ master: '002',
+ unionpay: '062',
+ visa: '001'
+ }
+
+ def initialize(options = {})
+ requires!(options, :merchant_id, :public_key, :private_key)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ authorize(money, payment, options, true)
+ end
+
+ def authorize(money, payment, options = {}, capture = false)
+ post = build_auth_request(money, payment, options)
+ post[:processingInformation] = { capture: true } if capture
+
+ commit('/pts/v2/payments/', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(signature=")[^"]*/, '\1[FILTERED]').
+ gsub(/(keyid=")[^"]*/, '\1[FILTERED]').
+ gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]')
+ end
+
+ private
+
+ def build_auth_request(amount, payment, options)
+ { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
+ add_customer_id(post, options)
+ add_code(post, options)
+ add_credit_card(post, payment)
+ add_amount(post, amount)
+ add_address(post, payment, options[:billing_address], options, :billTo)
+ add_address(post, payment, options[:shipping_address], options, :shipTo)
+ end.compact
+ end
+
+ def add_code(post, options)
+ return unless options[:order_id].present?
+
+ post[:clientReferenceInformation][:code] = options[:order_id]
+ end
+
+ def add_customer_id(post, options)
+ return unless options[:customer_id].present?
+
+ post[:paymentInformation][:customer] = { customerId: options[:customer_id] }
+ end
+
+ def add_amount(post, amount)
+ currency = options[:currency] || currency(amount)
+
+ post[:orderInformation][:amountDetails] = {
+ totalAmount: localized_amount(amount, currency),
+ currency: currency
+ }
+ end
+
+ def add_credit_card(post, creditcard)
+ post[:paymentInformation][:card] = {
+ number: creditcard.number,
+ expirationMonth: format(creditcard.month, :two_digits),
+ expirationYear: format(creditcard.year, :four_digits),
+ securityCode: creditcard.verification_value,
+ type: CREDIT_CARD_CODES[card_brand(creditcard).to_sym]
+ }
+ end
+
+ def add_address(post, payment_method, address, options, address_type)
+ return unless address.present?
+
+ first_name, last_name = address_names(address[:name], payment_method)
+
+ post[:orderInformation][address_type] = {
+ firstName: first_name,
+ lastName: last_name,
+ address1: address[:address1],
+ address2: address[:address2],
+ locality: address[:city],
+ administrativeArea: address[:state],
+ postalCode: address[:zip],
+ country: lookup_country_code(address[:country])&.value,
+ email: options[:email].presence || 'null@cybersource.com',
+ phoneNumber: address[:phone]
+ # merchantTaxID: ship_to ? options[:merchant_tax_id] : nil,
+ # company: address[:company],
+ # companyTaxID: address[:companyTaxID],
+ # ipAddress: options[:ip],
+ # driversLicenseNumber: options[:drivers_license_number],
+ # driversLicenseState: options[:drivers_license_state],
+ }.compact
+ end
+
+ def url(action)
+ "#{(test? ? test_url : live_url)}#{action}"
+ end
+
+ def host
+ URI.parse(url('')).host
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, post)
+ response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')),
+ # cvv_result: CVVResult.new(response['some_cvv_response_key']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ActiveMerchant::ResponseError => e
+ response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } }
+ Response.new(false, response.dig('response', 'rmsg'), response, test: test?)
+ end
+
+ def success_from(response)
+ response['status'] == 'AUTHORIZED'
+ end
+
+ def message_from(response)
+ return response['status'] if success_from(response)
+
+ response['errorInformation']['message']
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def error_code_from(response)
+ response['errorInformation']['reason'] unless success_from(response)
+ end
+
+ # This implementation follows the Cybersource guide on how create the request signature, see:
+ # https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/GenerateHeader/httpSignatureAuthentication.html
+ def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate)
+ string_to_sign = {
+ host: host,
+ date: gmtdatetime,
+ "(request-target)": "#{http_method} #{resource}",
+ digest: digest,
+ "v-c-merchant-id": @options[:merchant_id]
+ }.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8)
+
+ {
+ keyid: @options[:public_key],
+ algorithm: 'HmacSHA256',
+ headers: "host date (request-target)#{digest.present? ? ' digest' : ''} v-c-merchant-id",
+ signature: sign_payload(string_to_sign)
+ }.map { |k, v| %{#{k}="#{v}"} }.join(', ')
+ end
+
+ def sign_payload(payload)
+ decoded_key = Base64.decode64(@options[:private_key])
+ Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload))
+ end
+
+ def auth_headers(action, post, http_method = 'post')
+ digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present?
+ date = Time.now.httpdate
+
+ {
+ 'Accept' => 'application/hal+json;charset=utf-8',
+ 'Content-Type' => 'application/json;charset=utf-8',
+ 'V-C-Merchant-Id' => @options[:merchant_id],
+ 'Date' => date,
+ 'Host' => host,
+ 'Signature' => get_http_signature(action, digest, http_method, date),
+ 'Digest' => digest
+ }
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 66606cf5e6d..82237b04bf9 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -272,6 +272,12 @@ cyber_source_latam_pe:
login: merchant_id
password: soap_key
+# Working credentials, no need to replace
+cybersource_rest:
+ merchant_id: "testrest"
+ public_key: "08c94330-f618-42a3-b09d-e1e43be5efda"
+ private_key: "yBJxy6LjM2TmcPGu+GaJrHtkke25fPpUX+UY6/L/1tE="
+
# Working credentials, no need to replace
d_local:
login: aeaf9bbfa1
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
new file mode 100644
index 00000000000..06b688a3a2e
--- /dev/null
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -0,0 +1,82 @@
+require 'test_helper'
+
+class RemoteCyberSourceRestTest < Test::Unit::TestCase
+ def setup
+ @gateway = CyberSourceRestGateway.new(fixtures(:cybersource_rest))
+ @amount = 10221
+ @card_without_funds = credit_card('42423482938483873')
+ @visa_card = credit_card('4111111111111111',
+ verification_value: '987',
+ month: 12,
+ year: 2031)
+
+ @billing_address = {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ }
+
+ @options = {
+ order_id: generate_unique_id,
+ currency: 'USD',
+ email: 'test@cybs.com'
+ }
+ end
+
+ def test_handle_credentials_error
+ gateway = CyberSourceRestGateway.new({ merchant_id: 'abc123', public_key: 'abc456', private_key: 'def789' })
+ response = gateway.authorize(@amount, @visa_card, @options)
+
+ assert_equal('Authentication Failed', response.message)
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_billing_address
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_failure_authorize_with_declined_credit_card
+ response = @gateway.authorize(@amount, @card_without_funds, @options)
+
+ assert_failure response
+ assert_match %r{Invalid account}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.authorize(@amount, @visa_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(@visa_card.number, transcript)
+ assert_scrubbed(@visa_card.verification_value, transcript)
+ end
+end
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
new file mode 100644
index 00000000000..70258beb9d2
--- /dev/null
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -0,0 +1,262 @@
+require 'test_helper'
+
+class CyberSourceRestTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = CyberSourceRestGateway.new(
+ merchant_id: 'abc123',
+ public_key: 'def345',
+ private_key: "NYlM1sgultLjvgaraWvDCXykdz1buqOW8yXE3pMlmxQ=\n"
+ )
+ @credit_card = credit_card('4111111111111111',
+ verification_value: '987',
+ month: 12,
+ year: 2031)
+ @amount = 100
+ @options = {
+ order_id: '1',
+ description: 'Store Purchase',
+ billing_address: {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ },
+ email: 'test@cybs.com'
+ }
+
+ @gmt_time = Time.now.httpdate
+ @digest = 'SHA-256=gXWufV4Zc7VkN9Wkv9jh/JuAVclqDusx3vkyo3uJFWU='
+ @resource = '/pts/v2/payments/'
+ end
+
+ def test_required_merchant_id_and_secret
+ error = assert_raises(ArgumentError) { CyberSourceRestGateway.new }
+ assert_equal 'Missing required parameter: merchant_id', error.message
+ end
+
+ def test_supported_card_types
+ assert_equal CyberSourceRestGateway.supported_cardtypes, %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada]
+ end
+
+ def test_properly_format_on_zero_decilmal
+ stub_comms do
+ @gateway.authorize(1000, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ card = request['paymentInformation']['card']
+ amount_details = request['orderInformation']['amountDetails']
+
+ assert_equal '1', request['clientReferenceInformation']['code']
+ assert_equal '2031', card['expirationYear']
+ assert_equal '12', card['expirationMonth']
+ assert_equal '987', card['securityCode']
+ assert_equal '001', card['type']
+ assert_equal 'USD', amount_details['currency']
+ assert_equal '10.00', amount_details['totalAmount']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_should_create_an_http_signature_for_a_post
+ signature = @gateway.send :get_http_signature, @resource, @digest, 'post', @gmt_time
+
+ parsed = parse_signature(signature)
+
+ assert_equal 'def345', parsed['keyid']
+ assert_equal 'HmacSHA256', parsed['algorithm']
+ assert_equal 'host date (request-target) digest v-c-merchant-id', parsed['headers']
+ assert_equal %w[algorithm headers keyid signature], signature.split(', ').map { |v| v.split('=').first }.sort
+ end
+
+ def test_should_create_an_http_signature_for_a_get
+ signature = @gateway.send :get_http_signature, @resource, nil, 'get', @gmt_time
+
+ parsed = parse_signature(signature)
+ assert_equal 'host date (request-target) v-c-merchant-id', parsed['headers']
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_including_customer_if_customer_id_present
+ post = { paymentInformation: {} }
+
+ @gateway.send :add_customer_id, post, {}
+ assert_nil post[:paymentInformation][:customer]
+
+ @gateway.send :add_customer_id, post, { customer_id: 10 }
+ assert_equal 10, post[:paymentInformation][:customer][:customerId]
+ end
+
+ def test_add_ammount_and_currency
+ post = { orderInformation: {} }
+
+ @gateway.send :add_amount, post, 10221
+
+ assert_equal '102.21', post.dig(:orderInformation, :amountDetails, :totalAmount)
+ assert_equal 'USD', post.dig(:orderInformation, :amountDetails, :currency)
+ end
+
+ def test_add_credit_card_data
+ post = { paymentInformation: {} }
+ @gateway.send :add_credit_card, post, @credit_card
+
+ card = post[:paymentInformation][:card]
+ assert_equal @credit_card.number, card[:number]
+ assert_equal '2031', card[:expirationYear]
+ assert_equal '12', card[:expirationMonth]
+ assert_equal '987', card[:securityCode]
+ assert_equal '001', card[:type]
+ end
+
+ def test_add_billing_address
+ post = { orderInformation: {} }
+
+ @gateway.send :add_address, post, @credit_card, @options[:billing_address], @options, :billTo
+
+ address = post[:orderInformation][:billTo]
+
+ assert_equal 'John', address[:firstName]
+ assert_equal 'Doe', address[:lastName]
+ assert_equal '1 Market St', address[:address1]
+ assert_equal 'san francisco', address[:locality]
+ assert_equal 'US', address[:country]
+ assert_equal 'test@cybs.com', address[:email]
+ assert_equal '4158880000', address[:phoneNumber]
+ end
+
+ def test_add_shipping_address
+ post = { orderInformation: {} }
+ @options[:shipping_address] = @options.delete(:billing_address)
+
+ @gateway.send :add_address, post, @credit_card, @options[:shipping_address], @options, :shipTo
+
+ address = post[:orderInformation][:shipTo]
+
+ assert_equal 'John', address[:firstName]
+ assert_equal 'Doe', address[:lastName]
+ assert_equal '1 Market St', address[:address1]
+ assert_equal 'san francisco', address[:locality]
+ assert_equal 'US', address[:country]
+ assert_equal 'test@cybs.com', address[:email]
+ assert_equal '4158880000', address[:phoneNumber]
+ end
+
+ def test_url_building
+ assert_equal "#{@gateway.class.test_url}/action", @gateway.send(:url, '/action')
+ end
+
+ private
+
+ def parse_signature(signature)
+ signature.gsub(/=\"$/, '').delete('"').split(', ').map { |x| x.split('=') }.to_h
+ end
+
+ def pre_scrubbed
+ <<-PRE
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"08c94330-f618-42a3-b09d-e1e43be5efda\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"DJHeHWceVrsJydd8BCbGowr9dzQ/ry5cGN1FocLakEw=\"\r\nDigest: SHA-256=wuV1cxGzs6KpuUKJmlD7pKV6MZ/5G1wQVoYbf8cRChM=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"4111111111111111\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"987\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}"
+ PRE
+ end
+
+ def post_scrubbed
+ <<-POST
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"[FILTERED]\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"[FILTERED]\"\r\nDigest: SHA-256=[FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"[FILTERED]\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"[FILTERED]\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}"
+ POST
+ end
+
+ def successful_purchase_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "authReversal": {
+ "method": "POST",
+ "href": "/pts/v2/payments/6750124114786780104953/reversals"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/payments/6750124114786780104953"
+ },
+ "capture": {
+ "method": "POST",
+ "href": "/pts/v2/payments/6750124114786780104953/captures"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "b8779865d140125036016a0f85db907f"
+ },
+ "id": "6750124114786780104953",
+ "orderInformation": {
+ "amountDetails": {
+ "authorizedAmount": "102.21",
+ "currency": "USD"
+ }
+ },
+ "paymentAccountInformation": {
+ "card": {
+ "type": "001"
+ }
+ },
+ "paymentInformation": {
+ "tokenizedCard": {
+ "type": "001"
+ },
+ "card": {
+ "type": "001"
+ }
+ },
+ "pointOfSaleInformation": {
+ "terminalId": "111111"
+ },
+ "processorInformation": {
+ "approvalCode": "888888",
+ "networkTransactiDDDonId": "123456789619999",
+ "transactionId": "123456789619999",
+ "responseCode": "100",
+ "avs": {
+ "code": "X",
+ "codeRaw": "I1"
+ }
+ },
+ "reconciliationId": "78243988SD9YL291",
+ "status": "AUTHORIZED",
+ "submitTimeUtc": "2023-01-29T17:13:31Z"
+ }
+ RESPONSE
+ end
+end
From e769cdb908d4a8543bc68f13a4f7aa04c60daf59 Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Fri, 17 Feb 2023 11:12:50 -0500
Subject: [PATCH 013/390] CheckoutV2: Add store/unstore (#4677)
Summary:
------------------------------
In order to use Third Party Vaulting (TPV), this commit adds store and unstore methods.
For ApplePay (and NT in general) the verify method is used, to get a similar response, and
formatted to be equal to the store response using the /instruments endpoint.
Remote test
-----------------------
Finished in 240.268913 seconds.
82 tests, 203 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
97.561% passed
Unit test
-----------------------
Finished in 0.268913 seconds.
52 tests, 289 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop
-----------------------
756 files inspected, no offenses detected
Co-authored-by: Gustavo Sanmartin
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
.../billing/gateways/checkout_v2.rb | 115 ++++++++--
test/fixtures.yml | 4 +
.../gateways/remote_checkout_v2_test.rb | 75 +++++++
test/unit/gateways/checkout_v2_test.rb | 200 +++++++++++-------
5 files changed, 292 insertions(+), 103 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 839057d5721..7ba20bb0c50 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -65,6 +65,7 @@
* TrustCommerce: Verify feature added [jherreraa] #4692
* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
+* CheckoutV2: Add store/unstore [gasb150] #4677
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index bfefa82ce19..b64c8ed6184 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -19,12 +19,14 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
@access_token = nil
- begin
+
+ if options.has_key?(:secret_key)
requires!(options, :secret_key)
- rescue ArgumentError
+ else
requires!(options, :client_id, :client_secret)
@access_token = setup_access_token
end
+
super
end
@@ -39,7 +41,6 @@ def authorize(amount, payment_method, options = {})
post = {}
post[:capture] = false
build_auth_or_purchase(post, amount, payment_method, options)
-
options[:incremental_authorization] ? commit(:incremental_authorize, post, options[:incremental_authorization]) : commit(:authorize, post)
end
@@ -86,7 +87,7 @@ def verify(credit_card, options = {})
end
def verify_payment(authorization, option = {})
- commit(:verify_payment, authorization)
+ commit(:verify_payment, nil, authorization, :get)
end
def supports_scrubbing?
@@ -99,7 +100,34 @@ def scrub(transcript)
gsub(/("number\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cryptogram\\":\\")\w+/, '\1[FILTERED]').
- gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]')
+ gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]').
+ gsub(/("token\\":\\")\w+/, '\1[FILTERED]')
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+ MultiResponse.run do |r|
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
+ r.process { verify(payment_method, options) }
+ break r unless r.success?
+
+ r.params['source']['customer'] = r.params['customer']
+ r.process { response(:store, true, r.params['source']) }
+ else
+ r.process { tokenize(payment_method, options) }
+ break r unless r.success?
+
+ token = r.params['token']
+ add_payment_method(post, token, options)
+ post.merge!(post.delete(:source))
+ add_customer_data(post, options)
+ r.process { commit(:store, post) }
+ end
+ end
+ end
+
+ def unstore(id, options = {})
+ commit(:unstore, nil, id, :delete)
end
private
@@ -142,7 +170,8 @@ def add_metadata(post, options, payment_method = nil)
def add_payment_method(post, payment_method, options, key = :source)
post[key] = {}
- if payment_method.is_a?(NetworkTokenizationCreditCard)
+ case payment_method
+ when NetworkTokenizationCreditCard
token_type = token_type_from(payment_method)
cryptogram = payment_method.payment_cryptogram
eci = payment_method.eci || options[:eci]
@@ -153,7 +182,7 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:token_type] = token_type
post[key][:cryptogram] = cryptogram if cryptogram
post[key][:eci] = eci if eci
- elsif payment_method.is_a?(CreditCard)
+ when CreditCard
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
@@ -169,7 +198,14 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:last_name] = payment_method.last_name if payment_method.last_name
end
end
- unless payment_method.is_a?(String)
+ if payment_method.is_a?(String)
+ if /tok/.match?(payment_method)
+ post[:type] = 'token'
+ post[:token] = payment_method
+ else
+ add_source(post, options)
+ end
+ elsif payment_method.try(:year)
post[key][:expiry_year] = format(payment_method.year, :four_digits)
post[key][:expiry_month] = format(payment_method.month, :two_digits)
end
@@ -280,11 +316,12 @@ def setup_access_token
response['access_token']
end
- def commit(action, post, authorization = nil)
+ def commit(action, post, authorization = nil, method = :post)
begin
- raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers))
+ raw_response = ssl_request(method, url(action, authorization), post.to_json, headers(action))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
+ source_id = authorization if action == :unstore
rescue ResponseError => e
raise unless e.response.code.to_s =~ /4\d\d/
@@ -293,45 +330,62 @@ def commit(action, post, authorization = nil)
succeeded = success_from(action, response)
- response(action, succeeded, response)
+ response(action, succeeded, response, source_id)
end
- def response(action, succeeded, response)
+ def response(action, succeeded, response, source_id = nil)
successful_response = succeeded && action == :purchase || action == :authorize
avs_result = successful_response ? avs_result(response) : nil
cvv_result = successful_response ? cvv_result(response) : nil
-
+ authorization = authorization_from(response) unless action == :unstore
+ body = action == :unstore ? { response_code: response.to_s } : response
Response.new(
succeeded,
message_from(succeeded, response),
- response,
- authorization: authorization_from(response),
- error_code: error_code_from(succeeded, response),
+ body,
+ authorization: authorization,
+ error_code: error_code_from(succeeded, body),
test: test?,
avs_result: avs_result,
cvv_result: cvv_result
)
end
- def headers
+ def headers(action)
auth_token = @access_token ? "Bearer #{@access_token}" : @options[:secret_key]
+ auth_token = @options[:public_key] if action == :tokens
{
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
end
- def url(_post, action, authorization)
- if %i[authorize purchase credit].include?(action)
+ def tokenize(payment_method, options = {})
+ post = {}
+ add_authorization_type(post, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ commit(:tokens, post[:source])
+ end
+
+ def url(action, authorization)
+ case action
+ when :authorize, :purchase, :credit
"#{base_url}/payments"
- elsif action == :capture
+ when :unstore, :store
+ "#{base_url}/instruments/#{authorization}"
+ when :capture
"#{base_url}/payments/#{authorization}/captures"
- elsif action == :refund
+ when :refund
"#{base_url}/payments/#{authorization}/refunds"
- elsif action == :void
+ when :void
"#{base_url}/payments/#{authorization}/voids"
- elsif action == :incremental_authorize
+ when :incremental_authorize
"#{base_url}/payments/#{authorization}/authorizations"
+ when :tokens
+ "#{base_url}/tokens"
+ when :verify_payment
+ "#{base_url}/payments/#{authorization}"
else
"#{base_url}/payments/#{authorization}/#{action}"
end
@@ -363,7 +417,12 @@ def parse(body, error: nil)
def success_from(action, response)
return response['status'] == 'Pending' if action == :credit
+ return true if action == :unstore && response == 204
+ store_response = response['token'] || response['id']
+ if store_response
+ return true if (action == :tokens && store_response.match(/tok/)) || (action == :store && store_response.match(/src_/))
+ end
response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id')
end
@@ -416,6 +475,16 @@ def token_type_from(payment_method)
'applepay'
end
end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code after unstore(delete instrument), because the body is nil
+ when 200...300
+ response.body || response.code
+ else
+ raise ResponseError.new(response)
+ end
+ end
end
end
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 82237b04bf9..2a9b3103e69 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -198,6 +198,10 @@ checkout_v2:
client_id: CLIENT_ID_FOR_OAUTH_TRANSACTIONS
client_secret: CLIENT_SECRET_FOR_OAUTH_TRANSACTIONS
+checkout_v2_token:
+ secret_key: sk_sbox_xxxxxxxxxxxxxxxxx
+ public_key: pk_sbox_xxxxxxxxxxxxxxxxx
+
citrus_pay:
userid: CPF00001
password: 7c70414732de7e0ba3a04db5f24fcec8
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 4a212d8790c..dad88495c6b 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -3,8 +3,10 @@
class RemoteCheckoutV2Test < Test::Unit::TestCase
def setup
gateway_fixtures = fixtures(:checkout_v2)
+ gateway_token_fixtures = fixtures(:checkout_v2_token)
@gateway = CheckoutV2Gateway.new(secret_key: gateway_fixtures[:secret_key])
@gateway_oauth = CheckoutV2Gateway.new({ client_id: gateway_fixtures[:client_id], client_secret: gateway_fixtures[:client_secret] })
+ @gateway_token = CheckoutV2Gateway.new(secret_key: gateway_token_fixtures[:secret_key], public_key: gateway_token_fixtures[:public_key])
@amount = 200
@credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025')
@@ -134,6 +136,16 @@ def test_network_transaction_scrubbing
assert_scrubbed(@gateway.options[:secret_key], transcript)
end
+ def test_store_transcript_scrubbing
+ response = nil
+ transcript = capture_transcript(@gateway) do
+ response = @gateway_token.store(@credit_card, @options)
+ end
+ token = response.responses.first.params['token']
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(token, transcript)
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -583,6 +595,60 @@ def test_successful_credit
assert_equal 'Succeeded', response.message
end
+ def test_successful_store
+ response = @gateway_token.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_unstore_after_store
+ store = @gateway_token.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Succeeded', store.message
+ source_id = store.params['id']
+ response = @gateway_token.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_unstore_after_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_purchase_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ end
+
+ def test_successful_store_apple_pay
+ response = @gateway.store(@apple_pay_network_token, @options)
+ assert_success response
+ end
+
+ def test_successful_unstore_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ end
+
+ def test_success_store_with_google_pay
+ response = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
+ assert_success response
+ end
+
+ def test_failed_store_oauth
+ response = @gateway_oauth.store(@credit_card, @options)
+ assert_failure response
+ assert_equal '401: Unauthorized', response.message
+ end
+
def test_successful_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -648,6 +714,15 @@ def test_successful_void
assert_success void
end
+ def test_successful_purchase_store_after_verify
+ verify = @gateway.verify(@apple_pay_network_token, @options)
+ assert_success verify
+ source_id = verify.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ assert_success verify
+ end
+
def test_successful_void_via_oauth
auth = @gateway_oauth.authorize(@amount, @credit_card, @options)
assert_success auth
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 6a04bfbcbca..9d4fd14aa16 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -18,13 +18,16 @@ def setup
secret_key: '1111111111111'
)
@gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
-
+ @gateway_api = CheckoutV2Gateway.new({
+ secret_key: '1111111111111',
+ public_key: '2222222222222'
+ })
@credit_card = credit_card
@amount = 100
end
def test_successful_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -34,7 +37,7 @@ def test_successful_purchase
end
def test_successful_purchase_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -45,7 +48,7 @@ def test_successful_purchase_includes_avs_result
end
def test_successful_purchase_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -57,9 +60,9 @@ def test_successful_purchase_using_vts_network_token_without_eci
'4242424242424242',
{ source: :network_token, brand: 'visa' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -75,18 +78,18 @@ def test_successful_purchase_using_vts_network_token_without_eci
end
def test_successful_passing_processing_channel_id
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { processing_channel_id: '123456abcde' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['processing_channel_id'], '123456abcde')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_incremental_authorization
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card, { incremental_authorization: 'abcd1234' })
- end.check_request do |endpoint, _data, _headers|
+ end.check_request do |_method, endpoint, _data, _headers|
assert_include endpoint, 'abcd1234'
end.respond_with(successful_incremental_authorize_response)
@@ -94,18 +97,18 @@ def test_successful_passing_incremental_authorization
end
def test_successful_passing_authorization_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { authorization_type: 'Estimated' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['authorization_type'], 'Estimated')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_exemption_and_challenge_indicator
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['3ds']['exemption'], 'no_preference')
assert_equal(request_data['3ds']['challenge_indicator'], 'trusted_listing')
@@ -113,9 +116,9 @@ def test_successful_passing_exemption_and_challenge_indicator
end
def test_successful_passing_capture_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, 'abc', { capture_type: 'NonFinal' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['capture_type'], 'NonFinal')
end.respond_with(successful_capture_response)
@@ -126,9 +129,9 @@ def test_successful_purchase_using_vts_network_token_with_eci
'4242424242424242',
{ source: :network_token, brand: 'visa', eci: '06' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -148,9 +151,9 @@ def test_successful_purchase_using_mdes_network_token
'5436031030606378',
{ source: :network_token, brand: 'master' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -170,9 +173,9 @@ def test_successful_purchase_using_apple_pay_network_token
'4242424242424242',
{ source: :apple_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -192,9 +195,9 @@ def test_successful_purchase_using_android_pay_network_token
'4242424242424242',
{ source: :android_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -214,9 +217,9 @@ def test_successful_purchase_using_google_pay_network_token
'4242424242424242',
{ source: :google_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -236,9 +239,9 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
'4242424242424242',
{ source: :google_pay }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -266,7 +269,7 @@ def test_successful_render_for_oauth
end
def test_successful_authorize_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -277,7 +280,7 @@ def test_successful_authorize_includes_avs_result
end
def test_successful_authorize_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -285,9 +288,9 @@ def test_successful_authorize_includes_cvv_result
end
def test_purchase_with_additional_fields
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { descriptor_city: 'london', descriptor_name: 'sherlock' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(/"billing_descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data)
end.respond_with(successful_purchase_response)
@@ -297,16 +300,16 @@ def test_purchase_with_additional_fields
def test_successful_purchase_passing_metadata_with_mada_card_type
@credit_card.brand = 'mada'
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['metadata']['udf1'], 'mada')
end.respond_with(successful_purchase_response)
end
def test_failed_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(failed_purchase_response)
assert_failure response
@@ -314,14 +317,14 @@ def test_failed_purchase
end
def test_successful_authorize_and_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -329,7 +332,7 @@ def test_successful_authorize_and_capture
end
def test_successful_authorize_and_capture_with_additional_options
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
card_on_file: true,
transaction_indicator: 2,
@@ -340,7 +343,7 @@ def test_successful_authorize_and_capture_with_additional_options
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"stored":"true"}, data)
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"previous_payment_id":"pay_123"}, data)
@@ -351,7 +354,7 @@ def test_successful_authorize_and_capture_with_additional_options
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -359,7 +362,7 @@ def test_successful_authorize_and_capture_with_additional_options
end
def test_successful_purchase_with_stored_credentials
- initial_response = stub_comms do
+ initial_response = stub_comms(@gateway, :ssl_request) do
initial_options = {
stored_credential: {
initial_transaction: true,
@@ -367,7 +370,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, initial_options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"merchant_initiated":false}, data)
end.respond_with(successful_purchase_initial_stored_credential_response)
@@ -376,7 +379,7 @@ def test_successful_purchase_with_stored_credentials
assert_equal 'pay_7jcf4ovmwnqedhtldca3fjli2y', initial_response.params['id']
network_transaction_id = initial_response.params['id']
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false,
@@ -385,7 +388,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -396,7 +399,7 @@ def test_successful_purchase_with_stored_credentials
end
def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false
@@ -404,7 +407,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
merchant_initiated_transaction_id: 'pay_7jcf4ovmwnqedhtldca3fjli2y'
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -415,7 +418,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
end
def test_successful_purchase_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -423,7 +426,7 @@ def test_successful_purchase_with_metadata
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_using_stored_credential_response)
@@ -432,7 +435,7 @@ def test_successful_purchase_with_metadata
end
def test_successful_authorize_and_capture_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -440,7 +443,7 @@ def test_successful_authorize_and_capture_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -448,7 +451,7 @@ def test_successful_authorize_and_capture_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -456,14 +459,14 @@ def test_successful_authorize_and_capture_with_metadata
end
def test_moto_transaction_is_properly_set
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
manual_entry: true
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"MOTO"}, data)
end.respond_with(successful_authorize_response)
@@ -471,13 +474,13 @@ def test_moto_transaction_is_properly_set
end
def test_3ds_passed
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
callback_url: 'https://www.example.com'
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"success_url"}, data)
assert_match(%r{"failure_url"}, data)
end.respond_with(successful_authorize_response)
@@ -502,7 +505,7 @@ def test_failed_verify_payment
end
def test_successful_authorize_and_capture_with_3ds
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
attempt_n3d: true,
@@ -520,7 +523,7 @@ def test_successful_authorize_and_capture_with_3ds
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -528,7 +531,7 @@ def test_successful_authorize_and_capture_with_3ds
end
def test_successful_authorize_and_capture_with_3ds2
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
three_d_secure: {
@@ -545,7 +548,7 @@ def test_successful_authorize_and_capture_with_3ds2
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -553,7 +556,7 @@ def test_successful_authorize_and_capture_with_3ds2
end
def test_failed_authorize
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(failed_authorize_response)
@@ -563,7 +566,7 @@ def test_failed_authorize
end
def test_failed_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.capture(100, '')
end.respond_with(failed_capture_response)
@@ -571,14 +574,14 @@ def test_failed_capture
end
def test_successful_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms do
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -586,7 +589,7 @@ def test_successful_void
end
def test_successful_void_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -594,7 +597,7 @@ def test_successful_void_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -602,7 +605,7 @@ def test_successful_void_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms do
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -610,7 +613,7 @@ def test_successful_void_with_metadata
end
def test_failed_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.void('5d53a33d960c46d00f5dc061947d998c')
end.respond_with(failed_void_response)
assert_failure response
@@ -623,9 +626,9 @@ def test_successfully_passes_fund_type_and_fields
source_id: 'ca_spwmped4qmqenai7hcghquqle4',
account_holder_type: 'individual'
}
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.credit(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['instruction']['funds_transfer_type'], options[:funds_transfer_type]
assert_equal request['source']['type'], options[:source_type]
@@ -638,14 +641,14 @@ def test_successfully_passes_fund_type_and_fields
end
def test_successful_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -653,7 +656,7 @@ def test_successful_refund
end
def test_successful_refund_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -661,7 +664,7 @@ def test_successful_refund_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_response)
@@ -669,7 +672,7 @@ def test_successful_refund_with_metadata
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -677,7 +680,7 @@ def test_successful_refund_with_metadata
end
def test_failed_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.refund(nil, '')
end.respond_with(failed_refund_response)
@@ -685,7 +688,7 @@ def test_failed_refund
end
def test_successful_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(successful_verify_response)
assert_success response
@@ -693,13 +696,40 @@ def test_successful_verify
end
def test_failed_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(failed_verify_response)
assert_failure response
assert_equal 'request_invalid: card_number_invalid', response.message
end
+ def test_successful_store
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card)
+ end.check_request do |_method, endpoint, data, _headers|
+ if /tokens/.match?(endpoint)
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ elsif /instruments/.match?(endpoint)
+ assert_match(%r{"type":"token"}, data)
+ assert_match(%r{"token":"tok_}, data)
+ end
+ end.respond_with(succesful_token_response, succesful_store_response)
+ end
+
+ def test_successful_tokenize
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.send(:tokenize, @credit_card)
+ end.check_request do |_action, endpoint, data, _headers|
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ end.respond_with(succesful_token_response)
+ end
+
def test_transcript_scrubbing
assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed)
end
@@ -709,7 +739,7 @@ def test_network_transaction_scrubbing
end
def test_invalid_json
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(invalid_json_response)
@@ -718,7 +748,7 @@ def test_invalid_json
end
def test_error_code_returned
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(error_code_response)
@@ -727,7 +757,7 @@ def test_error_code_returned
end
def test_4xx_error_message
- @gateway.expects(:ssl_post).raises(error_4xx_response)
+ @gateway.expects(:ssl_request).raises(error_4xx_response)
assert response = @gateway.purchase(@amount, @credit_card)
@@ -775,6 +805,12 @@ def successful_purchase_response
)
end
+ def succesful_store_response
+ %(
+ {"id":"src_vzzqipykt5ke5odazx5d7nikii","type":"card","fingerprint":"9F3BAD2E48C6C8579F2F5DC0710B7C11A8ACD5072C3363A72579A6FB227D64BE","expiry_month":6,"expiry_year":2025,"scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","customer":{"id":"cus_gmthnluatgounpoiyzbmn5fvua", "email":"longbob.longsen@example.com"}}
+ )
+ end
+
def successful_purchase_with_network_token_response
purchase_response = JSON.parse(successful_purchase_response)
purchase_response['source']['payment_account_reference'] = '2FCFE326D92D4C27EDD699560F484'
@@ -1035,6 +1071,10 @@ def successful_verify_payment_response
)
end
+ def succesful_token_response
+ %({"type":"card","token":"tok_267wy4hwrpietkmbbp5iswwhvm","expires_on":"2023-01-03T20:18:49.0006481Z","expiry_month":6,"expiry_year":2025,"name":"Longbob Longsen","scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic"})
+ end
+
def failed_verify_payment_response
%(
{"id":"pay_xrwmaqlar73uhjtyoghc7bspa4","requested_on":"2019-08-14T18:32:50Z","source":{"type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"7863","fingerprint":"DC20145B78E242C561A892B83CB64471729D7A5063E5A5B341035713B8FDEC92","bin":"453962"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"EuyOZtgt8KI4tolEH8lqxCclWqz","status":"Declined","approved":false,"3ds":{"downgraded":false,"enrolled":"Y","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_bb4b7eu35sde7o33fq2xchv7oq","name":"Jane Doe"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4/actions"}}}
From 0b8c1976eb9992a77e0a18459548310dfd1f02dd Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Mon, 20 Feb 2023 13:02:50 -0500
Subject: [PATCH 014/390] Revert "CheckoutV2: Add store/unstore (#4677)"
(#4703)
This reverts commit e769cdb908d4a8543bc68f13a4f7aa04c60daf59.
---
CHANGELOG | 1 -
.../billing/gateways/checkout_v2.rb | 115 ++--------
test/fixtures.yml | 4 -
.../gateways/remote_checkout_v2_test.rb | 75 -------
test/unit/gateways/checkout_v2_test.rb | 200 +++++++-----------
5 files changed, 103 insertions(+), 292 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 7ba20bb0c50..839057d5721 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -65,7 +65,6 @@
* TrustCommerce: Verify feature added [jherreraa] #4692
* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
-* CheckoutV2: Add store/unstore [gasb150] #4677
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index b64c8ed6184..bfefa82ce19 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -19,14 +19,12 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
@access_token = nil
-
- if options.has_key?(:secret_key)
+ begin
requires!(options, :secret_key)
- else
+ rescue ArgumentError
requires!(options, :client_id, :client_secret)
@access_token = setup_access_token
end
-
super
end
@@ -41,6 +39,7 @@ def authorize(amount, payment_method, options = {})
post = {}
post[:capture] = false
build_auth_or_purchase(post, amount, payment_method, options)
+
options[:incremental_authorization] ? commit(:incremental_authorize, post, options[:incremental_authorization]) : commit(:authorize, post)
end
@@ -87,7 +86,7 @@ def verify(credit_card, options = {})
end
def verify_payment(authorization, option = {})
- commit(:verify_payment, nil, authorization, :get)
+ commit(:verify_payment, authorization)
end
def supports_scrubbing?
@@ -100,34 +99,7 @@ def scrub(transcript)
gsub(/("number\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cryptogram\\":\\")\w+/, '\1[FILTERED]').
- gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]').
- gsub(/("token\\":\\")\w+/, '\1[FILTERED]')
- end
-
- def store(payment_method, options = {})
- post = {}
- MultiResponse.run do |r|
- if payment_method.is_a?(NetworkTokenizationCreditCard)
- r.process { verify(payment_method, options) }
- break r unless r.success?
-
- r.params['source']['customer'] = r.params['customer']
- r.process { response(:store, true, r.params['source']) }
- else
- r.process { tokenize(payment_method, options) }
- break r unless r.success?
-
- token = r.params['token']
- add_payment_method(post, token, options)
- post.merge!(post.delete(:source))
- add_customer_data(post, options)
- r.process { commit(:store, post) }
- end
- end
- end
-
- def unstore(id, options = {})
- commit(:unstore, nil, id, :delete)
+ gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]')
end
private
@@ -170,8 +142,7 @@ def add_metadata(post, options, payment_method = nil)
def add_payment_method(post, payment_method, options, key = :source)
post[key] = {}
- case payment_method
- when NetworkTokenizationCreditCard
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
token_type = token_type_from(payment_method)
cryptogram = payment_method.payment_cryptogram
eci = payment_method.eci || options[:eci]
@@ -182,7 +153,7 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:token_type] = token_type
post[key][:cryptogram] = cryptogram if cryptogram
post[key][:eci] = eci if eci
- when CreditCard
+ elsif payment_method.is_a?(CreditCard)
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
@@ -198,14 +169,7 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:last_name] = payment_method.last_name if payment_method.last_name
end
end
- if payment_method.is_a?(String)
- if /tok/.match?(payment_method)
- post[:type] = 'token'
- post[:token] = payment_method
- else
- add_source(post, options)
- end
- elsif payment_method.try(:year)
+ unless payment_method.is_a?(String)
post[key][:expiry_year] = format(payment_method.year, :four_digits)
post[key][:expiry_month] = format(payment_method.month, :two_digits)
end
@@ -316,12 +280,11 @@ def setup_access_token
response['access_token']
end
- def commit(action, post, authorization = nil, method = :post)
+ def commit(action, post, authorization = nil)
begin
- raw_response = ssl_request(method, url(action, authorization), post.to_json, headers(action))
+ raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
- source_id = authorization if action == :unstore
rescue ResponseError => e
raise unless e.response.code.to_s =~ /4\d\d/
@@ -330,62 +293,45 @@ def commit(action, post, authorization = nil, method = :post)
succeeded = success_from(action, response)
- response(action, succeeded, response, source_id)
+ response(action, succeeded, response)
end
- def response(action, succeeded, response, source_id = nil)
+ def response(action, succeeded, response)
successful_response = succeeded && action == :purchase || action == :authorize
avs_result = successful_response ? avs_result(response) : nil
cvv_result = successful_response ? cvv_result(response) : nil
- authorization = authorization_from(response) unless action == :unstore
- body = action == :unstore ? { response_code: response.to_s } : response
+
Response.new(
succeeded,
message_from(succeeded, response),
- body,
- authorization: authorization,
- error_code: error_code_from(succeeded, body),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(succeeded, response),
test: test?,
avs_result: avs_result,
cvv_result: cvv_result
)
end
- def headers(action)
+ def headers
auth_token = @access_token ? "Bearer #{@access_token}" : @options[:secret_key]
- auth_token = @options[:public_key] if action == :tokens
{
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
end
- def tokenize(payment_method, options = {})
- post = {}
- add_authorization_type(post, options)
- add_payment_method(post, payment_method, options)
- add_customer_data(post, options)
- commit(:tokens, post[:source])
- end
-
- def url(action, authorization)
- case action
- when :authorize, :purchase, :credit
+ def url(_post, action, authorization)
+ if %i[authorize purchase credit].include?(action)
"#{base_url}/payments"
- when :unstore, :store
- "#{base_url}/instruments/#{authorization}"
- when :capture
+ elsif action == :capture
"#{base_url}/payments/#{authorization}/captures"
- when :refund
+ elsif action == :refund
"#{base_url}/payments/#{authorization}/refunds"
- when :void
+ elsif action == :void
"#{base_url}/payments/#{authorization}/voids"
- when :incremental_authorize
+ elsif action == :incremental_authorize
"#{base_url}/payments/#{authorization}/authorizations"
- when :tokens
- "#{base_url}/tokens"
- when :verify_payment
- "#{base_url}/payments/#{authorization}"
else
"#{base_url}/payments/#{authorization}/#{action}"
end
@@ -417,12 +363,7 @@ def parse(body, error: nil)
def success_from(action, response)
return response['status'] == 'Pending' if action == :credit
- return true if action == :unstore && response == 204
- store_response = response['token'] || response['id']
- if store_response
- return true if (action == :tokens && store_response.match(/tok/)) || (action == :store && store_response.match(/src_/))
- end
response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id')
end
@@ -475,16 +416,6 @@ def token_type_from(payment_method)
'applepay'
end
end
-
- def handle_response(response)
- case response.code.to_i
- # to get the response code after unstore(delete instrument), because the body is nil
- when 200...300
- response.body || response.code
- else
- raise ResponseError.new(response)
- end
- end
end
end
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 2a9b3103e69..82237b04bf9 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -198,10 +198,6 @@ checkout_v2:
client_id: CLIENT_ID_FOR_OAUTH_TRANSACTIONS
client_secret: CLIENT_SECRET_FOR_OAUTH_TRANSACTIONS
-checkout_v2_token:
- secret_key: sk_sbox_xxxxxxxxxxxxxxxxx
- public_key: pk_sbox_xxxxxxxxxxxxxxxxx
-
citrus_pay:
userid: CPF00001
password: 7c70414732de7e0ba3a04db5f24fcec8
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index dad88495c6b..4a212d8790c 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -3,10 +3,8 @@
class RemoteCheckoutV2Test < Test::Unit::TestCase
def setup
gateway_fixtures = fixtures(:checkout_v2)
- gateway_token_fixtures = fixtures(:checkout_v2_token)
@gateway = CheckoutV2Gateway.new(secret_key: gateway_fixtures[:secret_key])
@gateway_oauth = CheckoutV2Gateway.new({ client_id: gateway_fixtures[:client_id], client_secret: gateway_fixtures[:client_secret] })
- @gateway_token = CheckoutV2Gateway.new(secret_key: gateway_token_fixtures[:secret_key], public_key: gateway_token_fixtures[:public_key])
@amount = 200
@credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025')
@@ -136,16 +134,6 @@ def test_network_transaction_scrubbing
assert_scrubbed(@gateway.options[:secret_key], transcript)
end
- def test_store_transcript_scrubbing
- response = nil
- transcript = capture_transcript(@gateway) do
- response = @gateway_token.store(@credit_card, @options)
- end
- token = response.responses.first.params['token']
- transcript = @gateway.scrub(transcript)
- assert_scrubbed(token, transcript)
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -595,60 +583,6 @@ def test_successful_credit
assert_equal 'Succeeded', response.message
end
- def test_successful_store
- response = @gateway_token.store(@credit_card, @options)
- assert_success response
- assert_equal 'Succeeded', response.message
- end
-
- def test_successful_unstore_after_store
- store = @gateway_token.store(@credit_card, @options)
- assert_success store
- assert_equal 'Succeeded', store.message
- source_id = store.params['id']
- response = @gateway_token.unstore(source_id, @options)
- assert_success response
- assert_equal response.params['response_code'], '204'
- end
-
- def test_successful_unstore_after_purchase
- purchase = @gateway.purchase(@amount, @credit_card, @options)
- source_id = purchase.params['source']['id']
- response = @gateway.unstore(source_id, @options)
- assert_success response
- assert_equal response.params['response_code'], '204'
- end
-
- def test_successful_purchase_after_purchase_with_google_pay
- purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
- source_id = purchase.params['source']['id']
- response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
- assert_success response
- end
-
- def test_successful_store_apple_pay
- response = @gateway.store(@apple_pay_network_token, @options)
- assert_success response
- end
-
- def test_successful_unstore_after_purchase_with_google_pay
- purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
- source_id = purchase.params['source']['id']
- response = @gateway.unstore(source_id, @options)
- assert_success response
- end
-
- def test_success_store_with_google_pay
- response = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
- assert_success response
- end
-
- def test_failed_store_oauth
- response = @gateway_oauth.store(@credit_card, @options)
- assert_failure response
- assert_equal '401: Unauthorized', response.message
- end
-
def test_successful_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -714,15 +648,6 @@ def test_successful_void
assert_success void
end
- def test_successful_purchase_store_after_verify
- verify = @gateway.verify(@apple_pay_network_token, @options)
- assert_success verify
- source_id = verify.params['source']['id']
- response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
- assert_success response
- assert_success verify
- end
-
def test_successful_void_via_oauth
auth = @gateway_oauth.authorize(@amount, @credit_card, @options)
assert_success auth
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 9d4fd14aa16..6a04bfbcbca 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -18,16 +18,13 @@ def setup
secret_key: '1111111111111'
)
@gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
- @gateway_api = CheckoutV2Gateway.new({
- secret_key: '1111111111111',
- public_key: '2222222222222'
- })
+
@credit_card = credit_card
@amount = 100
end
def test_successful_purchase
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -37,7 +34,7 @@ def test_successful_purchase
end
def test_successful_purchase_includes_avs_result
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -48,7 +45,7 @@ def test_successful_purchase_includes_avs_result
end
def test_successful_purchase_includes_cvv_result
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -60,9 +57,9 @@ def test_successful_purchase_using_vts_network_token_without_eci
'4242424242424242',
{ source: :network_token, brand: 'visa' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -78,18 +75,18 @@ def test_successful_purchase_using_vts_network_token_without_eci
end
def test_successful_passing_processing_channel_id
- stub_comms(@gateway, :ssl_request) do
+ stub_comms do
@gateway.purchase(@amount, @credit_card, { processing_channel_id: '123456abcde' })
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['processing_channel_id'], '123456abcde')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_incremental_authorization
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card, { incremental_authorization: 'abcd1234' })
- end.check_request do |_method, endpoint, _data, _headers|
+ end.check_request do |endpoint, _data, _headers|
assert_include endpoint, 'abcd1234'
end.respond_with(successful_incremental_authorize_response)
@@ -97,18 +94,18 @@ def test_successful_passing_incremental_authorization
end
def test_successful_passing_authorization_type
- stub_comms(@gateway, :ssl_request) do
+ stub_comms do
@gateway.purchase(@amount, @credit_card, { authorization_type: 'Estimated' })
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['authorization_type'], 'Estimated')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_exemption_and_challenge_indicator
- stub_comms(@gateway, :ssl_request) do
+ stub_comms do
@gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' })
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['3ds']['exemption'], 'no_preference')
assert_equal(request_data['3ds']['challenge_indicator'], 'trusted_listing')
@@ -116,9 +113,9 @@ def test_successful_passing_exemption_and_challenge_indicator
end
def test_successful_passing_capture_type
- stub_comms(@gateway, :ssl_request) do
+ stub_comms do
@gateway.capture(@amount, 'abc', { capture_type: 'NonFinal' })
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['capture_type'], 'NonFinal')
end.respond_with(successful_capture_response)
@@ -129,9 +126,9 @@ def test_successful_purchase_using_vts_network_token_with_eci
'4242424242424242',
{ source: :network_token, brand: 'visa', eci: '06' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -151,9 +148,9 @@ def test_successful_purchase_using_mdes_network_token
'5436031030606378',
{ source: :network_token, brand: 'master' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -173,9 +170,9 @@ def test_successful_purchase_using_apple_pay_network_token
'4242424242424242',
{ source: :apple_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -195,9 +192,9 @@ def test_successful_purchase_using_android_pay_network_token
'4242424242424242',
{ source: :android_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -217,9 +214,9 @@ def test_successful_purchase_using_google_pay_network_token
'4242424242424242',
{ source: :google_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -239,9 +236,9 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
'4242424242424242',
{ source: :google_pay }
)
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, network_token)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -269,7 +266,7 @@ def test_successful_render_for_oauth
end
def test_successful_authorize_includes_avs_result
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -280,7 +277,7 @@ def test_successful_authorize_includes_avs_result
end
def test_successful_authorize_includes_cvv_result
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -288,9 +285,9 @@ def test_successful_authorize_includes_cvv_result
end
def test_purchase_with_additional_fields
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card, { descriptor_city: 'london', descriptor_name: 'sherlock' })
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(/"billing_descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data)
end.respond_with(successful_purchase_response)
@@ -300,16 +297,16 @@ def test_purchase_with_additional_fields
def test_successful_purchase_passing_metadata_with_mada_card_type
@credit_card.brand = 'mada'
- stub_comms(@gateway, :ssl_request) do
+ stub_comms do
@gateway.purchase(@amount, @credit_card)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['metadata']['udf1'], 'mada')
end.respond_with(successful_purchase_response)
end
def test_failed_purchase
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(failed_purchase_response)
assert_failure response
@@ -317,14 +314,14 @@ def test_failed_purchase
end
def test_successful_authorize_and_capture
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms(@gateway, :ssl_request) do
+ capture = stub_comms do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -332,7 +329,7 @@ def test_successful_authorize_and_capture
end
def test_successful_authorize_and_capture_with_additional_options
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
card_on_file: true,
transaction_indicator: 2,
@@ -343,7 +340,7 @@ def test_successful_authorize_and_capture_with_additional_options
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"stored":"true"}, data)
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"previous_payment_id":"pay_123"}, data)
@@ -354,7 +351,7 @@ def test_successful_authorize_and_capture_with_additional_options
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms(@gateway, :ssl_request) do
+ capture = stub_comms do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -362,7 +359,7 @@ def test_successful_authorize_and_capture_with_additional_options
end
def test_successful_purchase_with_stored_credentials
- initial_response = stub_comms(@gateway, :ssl_request) do
+ initial_response = stub_comms do
initial_options = {
stored_credential: {
initial_transaction: true,
@@ -370,7 +367,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, initial_options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"merchant_initiated":false}, data)
end.respond_with(successful_purchase_initial_stored_credential_response)
@@ -379,7 +376,7 @@ def test_successful_purchase_with_stored_credentials
assert_equal 'pay_7jcf4ovmwnqedhtldca3fjli2y', initial_response.params['id']
network_transaction_id = initial_response.params['id']
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
stored_credential: {
initial_transaction: false,
@@ -388,7 +385,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -399,7 +396,7 @@ def test_successful_purchase_with_stored_credentials
end
def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
stored_credential: {
initial_transaction: false
@@ -407,7 +404,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
merchant_initiated_transaction_id: 'pay_7jcf4ovmwnqedhtldca3fjli2y'
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -418,7 +415,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
end
def test_successful_purchase_with_metadata
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -426,7 +423,7 @@ def test_successful_purchase_with_metadata
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_using_stored_credential_response)
@@ -435,7 +432,7 @@ def test_successful_purchase_with_metadata
end
def test_successful_authorize_and_capture_with_metadata
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -443,7 +440,7 @@ def test_successful_authorize_and_capture_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -451,7 +448,7 @@ def test_successful_authorize_and_capture_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms(@gateway, :ssl_request) do
+ capture = stub_comms do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -459,14 +456,14 @@ def test_successful_authorize_and_capture_with_metadata
end
def test_moto_transaction_is_properly_set
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
metadata: {
manual_entry: true
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"payment_type":"MOTO"}, data)
end.respond_with(successful_authorize_response)
@@ -474,13 +471,13 @@ def test_moto_transaction_is_properly_set
end
def test_3ds_passed
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
execute_threed: true,
callback_url: 'https://www.example.com'
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"success_url"}, data)
assert_match(%r{"failure_url"}, data)
end.respond_with(successful_authorize_response)
@@ -505,7 +502,7 @@ def test_failed_verify_payment
end
def test_successful_authorize_and_capture_with_3ds
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
execute_threed: true,
attempt_n3d: true,
@@ -523,7 +520,7 @@ def test_successful_authorize_and_capture_with_3ds
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms(@gateway, :ssl_request) do
+ capture = stub_comms do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -531,7 +528,7 @@ def test_successful_authorize_and_capture_with_3ds
end
def test_successful_authorize_and_capture_with_3ds2
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
execute_threed: true,
three_d_secure: {
@@ -548,7 +545,7 @@ def test_successful_authorize_and_capture_with_3ds2
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms(@gateway, :ssl_request) do
+ capture = stub_comms do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -556,7 +553,7 @@ def test_successful_authorize_and_capture_with_3ds2
end
def test_failed_authorize
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(failed_authorize_response)
@@ -566,7 +563,7 @@ def test_failed_authorize
end
def test_failed_capture
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.capture(100, '')
end.respond_with(failed_capture_response)
@@ -574,14 +571,14 @@ def test_failed_capture
end
def test_successful_void
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms(@gateway, :ssl_request) do
+ void = stub_comms do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -589,7 +586,7 @@ def test_successful_void
end
def test_successful_void_with_metadata
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -597,7 +594,7 @@ def test_successful_void_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -605,7 +602,7 @@ def test_successful_void_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms(@gateway, :ssl_request) do
+ void = stub_comms do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -613,7 +610,7 @@ def test_successful_void_with_metadata
end
def test_failed_void
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.void('5d53a33d960c46d00f5dc061947d998c')
end.respond_with(failed_void_response)
assert_failure response
@@ -626,9 +623,9 @@ def test_successfully_passes_fund_type_and_fields
source_id: 'ca_spwmped4qmqenai7hcghquqle4',
account_holder_type: 'individual'
}
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.credit(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['instruction']['funds_transfer_type'], options[:funds_transfer_type]
assert_equal request['source']['type'], options[:source_type]
@@ -641,14 +638,14 @@ def test_successfully_passes_fund_type_and_fields
end
def test_successful_refund
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms(@gateway, :ssl_request) do
+ refund = stub_comms do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -656,7 +653,7 @@ def test_successful_refund
end
def test_successful_refund_with_metadata
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -664,7 +661,7 @@ def test_successful_refund_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_method, _endpoint, data, _headers|
+ end.check_request do |_endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_response)
@@ -672,7 +669,7 @@ def test_successful_refund_with_metadata
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms(@gateway, :ssl_request) do
+ refund = stub_comms do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -680,7 +677,7 @@ def test_successful_refund_with_metadata
end
def test_failed_refund
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.refund(nil, '')
end.respond_with(failed_refund_response)
@@ -688,7 +685,7 @@ def test_failed_refund
end
def test_successful_verify
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.verify(@credit_card)
end.respond_with(successful_verify_response)
assert_success response
@@ -696,40 +693,13 @@ def test_successful_verify
end
def test_failed_verify
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.verify(@credit_card)
end.respond_with(failed_verify_response)
assert_failure response
assert_equal 'request_invalid: card_number_invalid', response.message
end
- def test_successful_store
- stub_comms(@gateway, :ssl_request) do
- @gateway.store(@credit_card)
- end.check_request do |_method, endpoint, data, _headers|
- if /tokens/.match?(endpoint)
- assert_match(%r{"type":"card"}, data)
- assert_match(%r{"number":"4242424242424242"}, data)
- assert_match(%r{"cvv":"123"}, data)
- assert_match('/tokens', endpoint)
- elsif /instruments/.match?(endpoint)
- assert_match(%r{"type":"token"}, data)
- assert_match(%r{"token":"tok_}, data)
- end
- end.respond_with(succesful_token_response, succesful_store_response)
- end
-
- def test_successful_tokenize
- stub_comms(@gateway, :ssl_request) do
- @gateway.send(:tokenize, @credit_card)
- end.check_request do |_action, endpoint, data, _headers|
- assert_match(%r{"type":"card"}, data)
- assert_match(%r{"number":"4242424242424242"}, data)
- assert_match(%r{"cvv":"123"}, data)
- assert_match('/tokens', endpoint)
- end.respond_with(succesful_token_response)
- end
-
def test_transcript_scrubbing
assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed)
end
@@ -739,7 +709,7 @@ def test_network_transaction_scrubbing
end
def test_invalid_json
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(invalid_json_response)
@@ -748,7 +718,7 @@ def test_invalid_json
end
def test_error_code_returned
- response = stub_comms(@gateway, :ssl_request) do
+ response = stub_comms do
@gateway.purchase(@amount, @credit_card)
end.respond_with(error_code_response)
@@ -757,7 +727,7 @@ def test_error_code_returned
end
def test_4xx_error_message
- @gateway.expects(:ssl_request).raises(error_4xx_response)
+ @gateway.expects(:ssl_post).raises(error_4xx_response)
assert response = @gateway.purchase(@amount, @credit_card)
@@ -805,12 +775,6 @@ def successful_purchase_response
)
end
- def succesful_store_response
- %(
- {"id":"src_vzzqipykt5ke5odazx5d7nikii","type":"card","fingerprint":"9F3BAD2E48C6C8579F2F5DC0710B7C11A8ACD5072C3363A72579A6FB227D64BE","expiry_month":6,"expiry_year":2025,"scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","customer":{"id":"cus_gmthnluatgounpoiyzbmn5fvua", "email":"longbob.longsen@example.com"}}
- )
- end
-
def successful_purchase_with_network_token_response
purchase_response = JSON.parse(successful_purchase_response)
purchase_response['source']['payment_account_reference'] = '2FCFE326D92D4C27EDD699560F484'
@@ -1071,10 +1035,6 @@ def successful_verify_payment_response
)
end
- def succesful_token_response
- %({"type":"card","token":"tok_267wy4hwrpietkmbbp5iswwhvm","expires_on":"2023-01-03T20:18:49.0006481Z","expiry_month":6,"expiry_year":2025,"name":"Longbob Longsen","scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic"})
- end
-
def failed_verify_payment_response
%(
{"id":"pay_xrwmaqlar73uhjtyoghc7bspa4","requested_on":"2019-08-14T18:32:50Z","source":{"type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"7863","fingerprint":"DC20145B78E242C561A892B83CB64471729D7A5063E5A5B341035713B8FDEC92","bin":"453962"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"EuyOZtgt8KI4tolEH8lqxCclWqz","status":"Declined","approved":false,"3ds":{"downgraded":false,"enrolled":"Y","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_bb4b7eu35sde7o33fq2xchv7oq","name":"Jane Doe"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4/actions"}}}
From 6f785959bd9bf5f3f8b17551713e53b7b47dcf35 Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Tue, 21 Feb 2023 10:14:24 -0500
Subject: [PATCH 015/390] Moneris: Fix google pay (update apple pay) (#4689)
Description
-------------------------
Truncate google pay and apple pay order id to 100 (max length of characters that google pay / apple pay accepts)
Unit test
-------------------------
Finished in 37.884991 seconds.
5439 tests, 77066 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
143.57 tests/s, 2034.21 assertions/s
Rubocop
-------------------------
756 files inspected, no offenses detected
Co-authored-by: Luis
---
.../billing/gateways/moneris.rb | 18 +++-
test/remote/gateways/remote_moneris_test.rb | 85 ++++++++++++++-----
2 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb
index 1491e1315fa..b1148cce673 100644
--- a/lib/active_merchant/billing/gateways/moneris.rb
+++ b/lib/active_merchant/billing/gateways/moneris.rb
@@ -9,6 +9,8 @@ module Billing #:nodoc:
# Response Values", available at Moneris' {eSelect Plus Documentation
# Centre}[https://www3.moneris.com/connect/en/documents/index.html].
class MonerisGateway < Gateway
+ WALLETS = %w(APP GPP)
+
self.test_url = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
self.live_url = 'https://www3.moneris.com/gateway2/servlet/MpgRequest'
@@ -47,7 +49,7 @@ def authorize(money, creditcard_or_datakey, options = {})
post = {}
add_payment_source(post, creditcard_or_datakey, options)
post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
+ post[:order_id] = format_order_id(post[:wallet_indicator], options[:order_id])
post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
add_external_mpi_fields(post, options)
@@ -71,7 +73,7 @@ def purchase(money, creditcard_or_datakey, options = {})
post = {}
add_payment_source(post, creditcard_or_datakey, options)
post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
+ post[:order_id] = format_order_id(post[:wallet_indicator], options[:order_id])
post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
add_external_mpi_fields(post, options)
@@ -438,6 +440,18 @@ def wallet_indicator(token_source)
}[token_source]
end
+ def format_order_id(wallet_indicator_code, order_id = nil)
+ # Truncate (max 100 characters) order id for
+ # google pay and apple pay (specific wallets / token sources)
+ return truncate_order_id(order_id) if WALLETS.include?(wallet_indicator_code)
+
+ order_id
+ end
+
+ def truncate_order_id(order_id = nil)
+ order_id.present? ? order_id[0, 100] : SecureRandom.alphanumeric(100)
+ end
+
def message_from(message)
return 'Unspecified error' if message.blank?
diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb
index 11f2d8af78a..c63de177909 100644
--- a/test/remote/gateways/remote_moneris_test.rb
+++ b/test/remote/gateways/remote_moneris_test.rb
@@ -15,6 +15,15 @@ def setup
@no_liability_shift_eci = 7
@credit_card = credit_card('4242424242424242', verification_value: '012')
+ @network_tokenization_credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ @apple_pay_credit_card = @network_tokenization_credit_card
+ @apple_pay_credit_card.source = :apple_pay
+ @google_pay_credit_card = @network_tokenization_credit_card
+ @google_pay_credit_card.source = :google_pay
@visa_credit_card_3ds = credit_card('4606633870436092', verification_value: '012')
@options = {
order_id: generate_unique_id,
@@ -104,38 +113,74 @@ def test_successful_subsequent_purchase_with_credential_on_file
end
def test_successful_purchase_with_network_tokenization
- @credit_card = network_tokenization_credit_card(
- '4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil
- )
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, @network_tokenization_credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
end
def test_successful_purchase_with_network_tokenization_apple_pay_source
- @credit_card = network_tokenization_credit_card(
- '4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil,
- source: :apple_pay
- )
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, @apple_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_network_tokenization_apple_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.purchase(@amount, @apple_pay_credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
end
def test_successful_purchase_with_network_tokenization_google_pay_source
- @credit_card = network_tokenization_credit_card(
- '4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil,
- source: :google_pay
- )
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_network_tokenization_google_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.purchase(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization
+ assert response = @gateway.authorize(@amount, @network_tokenization_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_apple_pay_source
+ assert response = @gateway.authorize(@amount, @apple_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_apple_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.authorize(@amount, @apple_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_google_pay_source
+ assert response = @gateway.authorize(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_google_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.authorize(@amount, @google_pay_credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
From 34f84fc1ef276182017ee9eec894f7d94b0c6094 Mon Sep 17 00:00:00 2001
From: aenand
Date: Fri, 24 Feb 2023 09:54:56 -0500
Subject: [PATCH 016/390] Litle: Add prelive url
This commit adds a prelive URL to the Vantiv/Litle
gateway. It relies on the existing url_override logic
to set the prelive_url
Test Summary
Remote:
56 tests, 226 assertions, 13 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
76.7857% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/litle.rb | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 839057d5721..57407a8ac79 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -65,6 +65,7 @@
* TrustCommerce: Verify feature added [jherreraa] #4692
* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
+* Litle: Add prelive_url option [aenand] #4710
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb
index be6e34e9562..2c5e55ebc06 100644
--- a/lib/active_merchant/billing/gateways/litle.rb
+++ b/lib/active_merchant/billing/gateways/litle.rb
@@ -5,9 +5,10 @@ module Billing #:nodoc:
class LitleGateway < Gateway
SCHEMA_VERSION = '9.14'
- class_attribute :postlive_url
+ class_attribute :postlive_url, :prelive_url
self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online'
+ self.prelive_url = 'https://payments.vantivprelive.com/vap/communicator/online'
self.postlive_url = 'https://payments.vantivpostlive.com/vap/communicator/online'
self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online'
@@ -616,6 +617,7 @@ def build_xml_request
def url
return postlive_url if @options[:url_override].to_s == 'postlive'
+ return prelive_url if @options[:url_override].to_s == 'prelive'
test? ? test_url : live_url
end
From 86f2f6adc2d79208fd3a7dff9c23d84710373921 Mon Sep 17 00:00:00 2001
From: cristian
Date: Fri, 24 Feb 2023 11:00:44 -0500
Subject: [PATCH 017/390] CommerceHub: Adding changes for certification
purposes (#4705)
Summary:
------------------------------
Add changes to address some observations / improvements
in the CommerceHub certification.
* SER-494 / SER-498: setting and using order_id as reference
* SER-495: use the correct end-point for verify
* SER-496 / SER-497: get avs and cvv verification codes
* SER-500: getting an error code from response
Remote Test:
------------------------------
Finished in 108.050361 seconds.
23 tests, 63 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 46.103032 seconds.
5458 tests, 77155 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
.../billing/gateways/commerce_hub.rb | 70 ++++++++++++++-----
.../gateways/remote_commerce_hub_test.rb | 61 +++++++++++++++-
test/unit/gateways/commerce_hub_test.rb | 47 +++++++++++--
3 files changed, 152 insertions(+), 26 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 4db00a258e4..62225a6864f 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -18,7 +18,8 @@ class CommerceHubGateway < Gateway
'sale' => '/payments/v1/charges',
'void' => '/payments/v1/cancels',
'refund' => '/payments/v1/refunds',
- 'vault' => '/payments-vas/v1/tokens'
+ 'vault' => '/payments-vas/v1/tokens',
+ 'verify' => '/payments-vas/v1/accounts/verification'
}
def initialize(options = {})
@@ -29,7 +30,7 @@ def initialize(options = {})
def purchase(money, payment, options = {})
post = {}
options[:capture_flag] = true
- add_transaction_details(post, options)
+ add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
commit('sale', post, options)
@@ -38,7 +39,7 @@ def purchase(money, payment, options = {})
def authorize(money, payment, options = {})
post = {}
options[:capture_flag] = false
- add_transaction_details(post, options)
+ add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
commit('sale', post, options)
@@ -82,10 +83,11 @@ def store(credit_card, options = {})
end
def verify(credit_card, options = {})
- verify_amount = options[:verify_amount] || 0
- options[:primary_transaction_type] = 'AUTH_ONLY'
- options[:account_verification] = true
- authorize(verify_amount, credit_card, options)
+ post = {}
+ add_payment(post, credit_card, options)
+ add_billing_address(post, credit_card, options)
+
+ commit('verify', post, options)
end
def supports_scrubbing?
@@ -113,6 +115,12 @@ def add_transaction_interaction(post, options)
def add_transaction_details(post, options, action = nil)
post[:transactionDetails] = {}
post[:transactionDetails][:captureFlag] = options[:capture_flag] unless options[:capture_flag].nil?
+
+ if options[:order_id].present? && action == 'sale'
+ post[:transactionDetails][:merchantOrderId] = options[:order_id]
+ post[:transactionDetails][:merchantTransactionId] = options[:order_id]
+ end
+
if action != 'capture'
post[:transactionDetails][:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
post[:transactionDetails][:primaryTransactionType] = options[:primary_transaction_type] if options[:primary_transaction_type]
@@ -177,10 +185,14 @@ def build_purchase_and_auth_request(post, money, payment, options)
def add_reference_transaction_details(post, authorization, options, action = nil)
post[:referenceTransactionDetails] = {}
- post[:referenceTransactionDetails][:referenceTransactionId] = authorization
+ post[:referenceTransactionDetails][:referenceTransactionId] = authorization unless authorization.match?(/^order_id/)
+
if action != 'capture'
post[:referenceTransactionDetails][:referenceTransactionType] = options[:reference_transaction_type] || 'CHARGES'
- post[:referenceTransactionDetails][:referenceMerchantTransactionId] = options[:reference_merchant_transaction_id]
+
+ order_id = authorization.split('=').last if authorization.match?(/^order_id/)
+ post[:referenceTransactionDetails][:referenceMerchantTransactionId] = order_id || options[:reference_merchant_transaction_id]
+ post[:referenceTransactionDetails][:referenceMerchantOrderId] = order_id || options[:reference_merchant_order_id]
end
end
@@ -283,11 +295,24 @@ def commit(action, parameters, options)
Response.new(
success_from(response),
- message_from(response),
+ message_from(response, action),
response,
- authorization: authorization_from(action, response),
+ authorization: authorization_from(action, response, options),
test: test?,
- error_code: error_code_from(response)
+ error_code: error_code_from(response),
+ avs_result: AVSResult.new(code: get_avs_cvv(response, 'avs')),
+ cvv_result: CVVResult.new(get_avs_cvv(response, 'cvv'))
+ )
+ end
+
+ def get_avs_cvv(response, type = 'avs')
+ response.dig(
+ 'paymentReceipt',
+ 'processorResponseDetails',
+ 'bankAssociationDetails',
+ 'avsSecurityCodeResponse',
+ 'association',
+ type == 'avs' ? 'avsCode' : 'securityCodeResponse'
)
end
@@ -304,17 +329,26 @@ def success_from(response)
(response.dig('paymentReceipt', 'processorResponseDetails', 'responseCode') || response.dig('paymentTokens', 0, 'tokenResponseCode')) == '000'
end
- def message_from(response)
- response.dig('paymentReceipt', 'processorResponseDetails', 'responseMessage') || response.dig('error', 0, 'message') || response.dig('gatewayResponse', 'transactionType')
+ def message_from(response, action = nil)
+ return response.dig('error', 0, 'message') if response['error'].present?
+ return response.dig('gatewayResponse', 'transactionState') if action == 'verify'
+
+ response.dig('paymentReceipt', 'processorResponseDetails', 'responseMessage') || response.dig('gatewayResponse', 'transactionType')
end
- def authorization_from(action, response)
- return response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId') unless action == 'vault'
- return response.dig('paymentTokens', 0, 'tokenData') if action == 'vault'
+ def authorization_from(action, response, options)
+ case action
+ when 'vault'
+ response.dig('paymentTokens', 0, 'tokenData')
+ when 'sale'
+ options[:order_id].present? ? "order_id=#{options[:order_id]}" : response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
+ else
+ response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
+ end
end
def error_code_from(response)
- response.dig('error', 0, 'type') unless success_from(response)
+ response.dig('error', 0, 'code') unless success_from(response)
end
end
end
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index 1f8d32a453e..66e1c1985a9 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -2,10 +2,14 @@
class RemoteCommerceHubTest < Test::Unit::TestCase
def setup
+ # Uncomment the sleep if you want to run the entire set of remote tests without
+ # getting 'The transaction limit was exceeded. Please try again!' errors
+ # sleep 5
+
@gateway = CommerceHubGateway.new(fixtures(:commerce_hub))
@amount = 1204
- @credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '111')
+ @credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123', first_name: 'John', last_name: 'Doe')
@google_pay = network_tokenization_credit_card('4005550000000019',
brand: 'visa',
eci: '05',
@@ -37,6 +41,23 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_purchase_with_failed_avs_cvv_response_codes
+ @options[:billing_address] = {
+ address1: '112 Main St.',
+ city: 'Atlanta',
+ state: 'GA',
+ zip: '30301',
+ country: 'US'
+ }
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal 'X', response.cvv_result['code']
+ assert_equal 'CVV check not supported for card', response.cvv_result['message']
+ assert_nil response.avs_result['code']
+ end
+
def test_successful_purchase_with_billing_and_shipping
response = @gateway.purchase(@amount, @credit_card, @options.merge({ billing_address: address, shipping_address: address }))
assert_success response
@@ -67,6 +88,7 @@ def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_equal '104', response.error_code
end
def test_successful_authorize
@@ -100,6 +122,19 @@ def test_successful_authorize_and_void
assert_equal 'Approved', response.message
end
+ def test_successful_authorize_and_void_using_store_id_as_reference
+ @options[:order_id] = SecureRandom.hex(16)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal "order_id=#{@options[:order_id]}", response.authorization
+
+ response = @gateway.void(response.authorization, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
def test_failed_void
response = @gateway.void('123', @options)
assert_failure response
@@ -109,7 +144,28 @@ def test_failed_void
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'VERIFIED', response.message
+ end
+
+ def test_successful_verify_with_address
+ @options[:billing_address] = {
+ address1: '112 Main St.',
+ city: 'Atlanta',
+ state: 'GA',
+ zip: '30301',
+ country: 'US'
+ }
+
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'VERIFIED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+
+ assert_failure response
end
def test_successful_purchase_and_refund
@@ -182,7 +238,6 @@ def test_transcript_scrubbing
assert_scrubbed(@credit_card.number, transcript)
assert_scrubbed(@gateway.options[:api_key], transcript)
assert_scrubbed(@gateway.options[:api_secret], transcript)
- assert_scrubbed(@credit_card.verification_value, transcript)
end
def test_transcript_scrubbing_apple_pay
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index baa412181c1..43a0c2801f3 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -36,11 +36,14 @@ def setup
end
def test_successful_purchase
+ @options[:order_id] = 'abc123'
+
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['transactionDetails']['captureFlag'], true
+ assert_equal request['transactionDetails']['merchantOrderId'], 'abc123'
assert_equal request['merchantDetails']['terminalId'], @gateway.options[:terminal_id]
assert_equal request['merchantDetails']['merchantId'], @gateway.options[:merchant_id]
assert_equal request['amount']['total'], (@amount / 100.0).to_f
@@ -125,7 +128,7 @@ def test_failed_purchase_and_authorize
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'HOST', response.error_code
+ assert_equal 'string', response.error_code
end
def test_successful_parsing_of_billing_and_shipping_addresses
@@ -208,21 +211,55 @@ def test_successful_store
def test_successful_verify
response = stub_comms do
@gateway.verify(@credit_card, @options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal request['transactionDetails']['captureFlag'], false
- assert_equal request['transactionDetails']['primaryTransactionType'], 'AUTH_ONLY'
- assert_equal request['transactionDetails']['accountVerification'], true
+ assert_match %r{verification}, endpoint
+ assert_equal request['source']['sourceType'], 'PaymentCard'
end.respond_with(successful_authorize_response)
assert_success response
end
+ def test_getting_avs_cvv_from_response
+ gateway_resp = {
+ 'paymentReceipt' => {
+ 'processorResponseDetails' => {
+ 'bankAssociationDetails' => {
+ 'associationResponseCode' => 'V000',
+ 'avsSecurityCodeResponse' => {
+ 'streetMatch' => 'NONE',
+ 'postalCodeMatch' => 'NONE',
+ 'securityCodeMatch' => 'NOT_CHECKED',
+ 'association' => {
+ 'securityCodeResponse' => 'X',
+ 'avsCode' => 'Y'
+ }
+ }
+ }
+ }
+ }
+ }
+
+ assert_equal 'X', @gateway.send(:get_avs_cvv, gateway_resp, 'cvv')
+ assert_equal 'Y', @gateway.send(:get_avs_cvv, gateway_resp, 'avs')
+ end
+
def test_successful_scrub
assert @gateway.supports_scrubbing?
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_uses_order_id_to_keep_transaction_references_when_provided
+ @options[:order_id] = 'abc123'
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ assert_equal 'order_id=abc123', response.authorization
+ end
+
private
def successful_purchase_response
From 93edf1ff833f54986332a590d3a98329ce0d9417 Mon Sep 17 00:00:00 2001
From: cristian
Date: Mon, 27 Feb 2023 14:40:59 -0500
Subject: [PATCH 018/390] CommerceHub: Fixing verify status and prevent
tokenization (#4716)
Summary:
------------------------------
* SER-495: Fix success detection for verify
* SER-507: set default `create_token` for purchase and
authorize transactions
Remote Test:
------------------------------
Finished in 290.029817 seconds.
23 tests, 64 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 46.103032 seconds.
5458 tests, 77155 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/commerce_hub.rb | 31 ++++++++++++-------
test/unit/gateways/commerce_hub_test.rb | 25 +++++++++++++--
3 files changed, 42 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 57407a8ac79..1ed12ffe7c1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -66,6 +66,7 @@
* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
* Litle: Add prelive_url option [aenand] #4710
+* CommerceHub: Fixing verify status and prevent tokenization [heavyblade] #4716
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 62225a6864f..57a1e8b2df7 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -30,6 +30,8 @@ def initialize(options = {})
def purchase(money, payment, options = {})
post = {}
options[:capture_flag] = true
+ options[:create_token] = false
+
add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
@@ -39,6 +41,8 @@ def purchase(money, payment, options = {})
def authorize(money, payment, options = {})
post = {}
options[:capture_flag] = false
+ options[:create_token] = false
+
add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
@@ -113,19 +117,20 @@ def add_transaction_interaction(post, options)
end
def add_transaction_details(post, options, action = nil)
- post[:transactionDetails] = {}
- post[:transactionDetails][:captureFlag] = options[:capture_flag] unless options[:capture_flag].nil?
+ details = { captureFlag: options[:capture_flag], createToken: options[:create_token] }
if options[:order_id].present? && action == 'sale'
- post[:transactionDetails][:merchantOrderId] = options[:order_id]
- post[:transactionDetails][:merchantTransactionId] = options[:order_id]
+ details[:merchantOrderId] = options[:order_id]
+ details[:merchantTransactionId] = options[:order_id]
end
if action != 'capture'
- post[:transactionDetails][:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
- post[:transactionDetails][:primaryTransactionType] = options[:primary_transaction_type] if options[:primary_transaction_type]
- post[:transactionDetails][:accountVerification] = options[:account_verification] unless options[:account_verification].nil?
+ details[:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
+ details[:primaryTransactionType] = options[:primary_transaction_type]
+ details[:accountVerification] = options[:account_verification]
end
+
+ post[:transactionDetails] = details.compact
end
def add_billing_address(post, payment, options)
@@ -294,12 +299,12 @@ def commit(action, parameters, options)
response = parse(ssl_post(url, parameters.to_json, headers(parameters.to_json, options)))
Response.new(
- success_from(response),
+ success_from(response, action),
message_from(response, action),
response,
authorization: authorization_from(action, response, options),
test: test?,
- error_code: error_code_from(response),
+ error_code: error_code_from(response, action),
avs_result: AVSResult.new(code: get_avs_cvv(response, 'avs')),
cvv_result: CVVResult.new(get_avs_cvv(response, 'cvv'))
)
@@ -325,7 +330,9 @@ def handle_response(response)
end
end
- def success_from(response)
+ def success_from(response, action = nil)
+ return message_from(response, action) == 'VERIFIED' if action == 'verify'
+
(response.dig('paymentReceipt', 'processorResponseDetails', 'responseCode') || response.dig('paymentTokens', 0, 'tokenResponseCode')) == '000'
end
@@ -347,8 +354,8 @@ def authorization_from(action, response, options)
end
end
- def error_code_from(response)
- response.dig('error', 0, 'code') unless success_from(response)
+ def error_code_from(response, action)
+ response.dig('error', 0, 'code') unless success_from(response, action)
end
end
end
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index 43a0c2801f3..e4fefa90a70 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -43,6 +43,7 @@ def test_successful_purchase
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['transactionDetails']['captureFlag'], true
+ assert_equal request['transactionDetails']['createToken'], false
assert_equal request['transactionDetails']['merchantOrderId'], 'abc123'
assert_equal request['merchantDetails']['terminalId'], @gateway.options[:terminal_id]
assert_equal request['merchantDetails']['merchantId'], @gateway.options[:merchant_id]
@@ -209,15 +210,13 @@ def test_successful_store
end
def test_successful_verify
- response = stub_comms do
+ stub_comms do
@gateway.verify(@credit_card, @options)
end.check_request do |endpoint, data, _headers|
request = JSON.parse(data)
assert_match %r{verification}, endpoint
assert_equal request['source']['sourceType'], 'PaymentCard'
end.respond_with(successful_authorize_response)
-
- assert_success response
end
def test_getting_avs_cvv_from_response
@@ -260,6 +259,26 @@ def test_uses_order_id_to_keep_transaction_references_when_provided
assert_equal 'order_id=abc123', response.authorization
end
+ def test_detect_success_state_for_verify_on_success_transaction
+ gateway_resp = {
+ 'gatewayResponse' => {
+ 'transactionState' => 'VERIFIED'
+ }
+ }
+
+ assert @gateway.send :success_from, gateway_resp, 'verify'
+ end
+
+ def test_detect_success_state_for_verify_on_failure_transaction
+ gateway_resp = {
+ 'gatewayResponse' => {
+ 'transactionState' => 'NOT_VERIFIED'
+ }
+ }
+
+ refute @gateway.send :success_from, gateway_resp, 'verify'
+ end
+
private
def successful_purchase_response
From 7f6d5a14503390be0166f0d599a1c5c10bb5b92d Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 24 Feb 2023 11:10:30 -0600
Subject: [PATCH 019/390] Payeezy: Update Stored Credentials
Payeezy requires sending original network transaction id to as
cardbrand_original_transaction_id.
Unit:
47 tests, 217 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/payeezy.rb | 3 +-
test/unit/gateways/payeezy_test.rb | 40 +++++++++++++++++--
3 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1ed12ffe7c1..3601aea4508 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -67,6 +67,7 @@
* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
* Litle: Add prelive_url option [aenand] #4710
* CommerceHub: Fixing verify status and prevent tokenization [heavyblade] #4716
+* Payeezy: Update Stored Credentials [almalee24] #4711
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index cc1221f2cc1..8906ab0a606 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -307,8 +307,7 @@ def add_stored_credentials(params, options)
end
def original_transaction_id(options)
- return options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id]
- return options[:stored_credential][:network_transaction_id] if options.dig(:stored_credential, :network_transaction_id)
+ return options[:cardbrand_original_transaction_id] || options.dig(:stored_credential, :network_transaction_id)
end
def initiator(options)
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index edf8b815ec9..a5ba8f063a3 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -16,7 +16,7 @@ def setup
ta_token: '123'
}
@options_stored_credentials = {
- cardbrand_original_transaction_id: 'abc123',
+ cardbrand_original_transaction_id: 'original_transaction_id_abc123',
sequence: 'FIRST',
is_scheduled: true,
initiator: 'MERCHANT',
@@ -24,7 +24,7 @@ def setup
}
@options_standardized_stored_credentials = {
stored_credential: {
- network_transaction_id: 'abc123',
+ network_transaction_id: 'stored_credential_abc123',
initial_transaction: false,
reason_type: 'recurring',
initiator: 'cardholder'
@@ -205,7 +205,8 @@ def test_successful_purchase_with_stored_credentials
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials))
end.check_request do |_endpoint, data, _headers|
- assert_match(/stored_credentials/, data)
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'original_transaction_id_abc123'
end.respond_with(successful_purchase_stored_credentials_response)
assert_success response
@@ -217,7 +218,38 @@ def test_successful_purchase_with_standardized_stored_credentials
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(@options_standardized_stored_credentials))
end.check_request do |_endpoint, data, _headers|
- assert_match(/stored_credentials/, data)
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'stored_credential_abc123'
+ end.respond_with(successful_purchase_stored_credentials_response)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with__stored_credential_and_cardbrand_original_transaction_id
+ options = @options_standardized_stored_credentials.merge!(cardbrand_original_transaction_id: 'original_transaction_id_abc123')
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ end.check_request do |_endpoint, data, _headers|
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'original_transaction_id_abc123'
+ end.respond_with(successful_purchase_stored_credentials_response)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with_no_ntid
+ @options_standardized_stored_credentials[:stored_credential].delete(:network_transaction_id)
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(@options_standardized_stored_credentials))
+ end.check_request do |_endpoint, data, _headers|
+ stored_credentials = JSON.parse(data)['stored_credentials']
+ assert_equal stored_credentials.include?(:cardbrand_original_transaction_id), false
end.respond_with(successful_purchase_stored_credentials_response)
assert_success response
From a0ee5199d799546faf583e4a5c7fbfcb19108f3e Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Tue, 28 Feb 2023 13:39:54 -0500
Subject: [PATCH 020/390] Remove raise ArgumentError on get requests (#4714)
The case for initiating a GET request raises a frivolous ArgumentError
when the code already ignores any attempt to pass in a body to the
request.
---
lib/active_merchant/connection.rb | 2 --
test/unit/connection_test.rb | 7 -------
2 files changed, 9 deletions(-)
diff --git a/lib/active_merchant/connection.rb b/lib/active_merchant/connection.rb
index c15ac5bb803..626881a136e 100644
--- a/lib/active_merchant/connection.rb
+++ b/lib/active_merchant/connection.rb
@@ -85,8 +85,6 @@ def request(method, body, headers = {})
result =
case method
when :get
- raise ArgumentError, 'GET requests do not support a request body' if body
-
http.get(endpoint.request_uri, headers)
when :post
debug body
diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb
index 3ef0b493486..338718a99b4 100644
--- a/test/unit/connection_test.rb
+++ b/test/unit/connection_test.rb
@@ -87,13 +87,6 @@ def test_successful_delete_with_body_request
assert_equal 'success', response.body
end
- def test_get_raises_argument_error_if_passed_data
- assert_raises(ArgumentError) do
- Net::HTTP.any_instance.expects(:start).returns(true)
- @connection.request(:get, 'data', {})
- end
- end
-
def test_request_raises_when_request_method_not_supported
assert_raises(ArgumentError) do
Net::HTTP.any_instance.expects(:start).returns(true)
From 6b83b8e0f9b4fd70713fefc3e1fd82cc7e9025dd Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Tue, 28 Feb 2023 14:00:22 -0500
Subject: [PATCH 021/390] ChekoutV2:Add store/unstore (#4712)
Summary:
------------------------------
In order to use Third Party Vaulting (TPV), this commit adds store and unstore methods.
For apple pay/google pay the actual credentials could work, but for credit cards, and
bank account the credentials required are secret_api_key and public_api_key.
This commits also, refactor the initialize method removing the rescue ArgumentError,
and modifing the commit method to use ssl_request.
Remote test
-----------------------
Finished in 164.163913 seconds.
88 tests, 216 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Unit test
-----------------------
Finished in 0.046893 seconds.
53 tests, 297 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop
-----------------------
756 files inspected, no offenses detected
Co-authored-by: Gustavo Sanmartin
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
.../billing/gateways/checkout_v2.rb | 118 ++++++++--
test/fixtures.yml | 4 +
.../gateways/remote_checkout_v2_test.rb | 125 ++++++++++-
test/unit/gateways/checkout_v2_test.rb | 209 +++++++++++-------
5 files changed, 345 insertions(+), 112 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3601aea4508..6c533693fd0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -68,6 +68,7 @@
* Litle: Add prelive_url option [aenand] #4710
* CommerceHub: Fixing verify status and prevent tokenization [heavyblade] #4716
* Payeezy: Update Stored Credentials [almalee24] #4711
+* CheckoutV2: Add store/unstore [gasb150] #4712
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index bfefa82ce19..5fd41ea62e7 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -19,12 +19,14 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
@access_token = nil
- begin
+
+ if options.has_key?(:secret_key)
requires!(options, :secret_key)
- rescue ArgumentError
+ else
requires!(options, :client_id, :client_secret)
@access_token = setup_access_token
end
+
super
end
@@ -39,7 +41,6 @@ def authorize(amount, payment_method, options = {})
post = {}
post[:capture] = false
build_auth_or_purchase(post, amount, payment_method, options)
-
options[:incremental_authorization] ? commit(:incremental_authorize, post, options[:incremental_authorization]) : commit(:authorize, post)
end
@@ -86,7 +87,7 @@ def verify(credit_card, options = {})
end
def verify_payment(authorization, option = {})
- commit(:verify_payment, authorization)
+ commit(:verify_payment, nil, authorization, :get)
end
def supports_scrubbing?
@@ -99,7 +100,34 @@ def scrub(transcript)
gsub(/("number\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cryptogram\\":\\")\w+/, '\1[FILTERED]').
- gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]')
+ gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]').
+ gsub(/("token\\":\\")\w+/, '\1[FILTERED]')
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+ MultiResponse.run do |r|
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
+ r.process { verify(payment_method, options) }
+ break r unless r.success?
+
+ r.params['source']['customer'] = r.params['customer']
+ r.process { response(:store, true, r.params['source']) }
+ else
+ r.process { tokenize(payment_method, options) }
+ break r unless r.success?
+
+ token = r.params['token']
+ add_payment_method(post, token, options)
+ post.merge!(post.delete(:source))
+ add_customer_data(post, options)
+ r.process { commit(:store, post) }
+ end
+ end
+ end
+
+ def unstore(id, options = {})
+ commit(:unstore, nil, id, :delete)
end
private
@@ -142,7 +170,8 @@ def add_metadata(post, options, payment_method = nil)
def add_payment_method(post, payment_method, options, key = :source)
post[key] = {}
- if payment_method.is_a?(NetworkTokenizationCreditCard)
+ case payment_method
+ when NetworkTokenizationCreditCard
token_type = token_type_from(payment_method)
cryptogram = payment_method.payment_cryptogram
eci = payment_method.eci || options[:eci]
@@ -153,7 +182,7 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:token_type] = token_type
post[key][:cryptogram] = cryptogram if cryptogram
post[key][:eci] = eci if eci
- elsif payment_method.is_a?(CreditCard)
+ when CreditCard
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
@@ -169,7 +198,17 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:last_name] = payment_method.last_name if payment_method.last_name
end
end
- unless payment_method.is_a?(String)
+ if payment_method.is_a?(String)
+ if /tok/.match?(payment_method)
+ post[:type] = 'token'
+ post[:token] = payment_method
+ elsif /src/.match?(payment_method)
+ post[key][:type] = 'id'
+ post[key][:id] = payment_method
+ else
+ add_source(post, options)
+ end
+ elsif payment_method.try(:year)
post[key][:expiry_year] = format(payment_method.year, :four_digits)
post[key][:expiry_month] = format(payment_method.month, :two_digits)
end
@@ -280,11 +319,12 @@ def setup_access_token
response['access_token']
end
- def commit(action, post, authorization = nil)
+ def commit(action, post, authorization = nil, method = :post)
begin
- raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers))
+ raw_response = ssl_request(method, url(action, authorization), post.nil? || post.empty? ? nil : post.to_json, headers(action))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
+ source_id = authorization if action == :unstore
rescue ResponseError => e
raise unless e.response.code.to_s =~ /4\d\d/
@@ -293,45 +333,62 @@ def commit(action, post, authorization = nil)
succeeded = success_from(action, response)
- response(action, succeeded, response)
+ response(action, succeeded, response, source_id)
end
- def response(action, succeeded, response)
+ def response(action, succeeded, response, source_id = nil)
successful_response = succeeded && action == :purchase || action == :authorize
avs_result = successful_response ? avs_result(response) : nil
cvv_result = successful_response ? cvv_result(response) : nil
-
+ authorization = authorization_from(response) unless action == :unstore
+ body = action == :unstore ? { response_code: response.to_s } : response
Response.new(
succeeded,
message_from(succeeded, response),
- response,
- authorization: authorization_from(response),
- error_code: error_code_from(succeeded, response),
+ body,
+ authorization: authorization,
+ error_code: error_code_from(succeeded, body),
test: test?,
avs_result: avs_result,
cvv_result: cvv_result
)
end
- def headers
+ def headers(action)
auth_token = @access_token ? "Bearer #{@access_token}" : @options[:secret_key]
+ auth_token = @options[:public_key] if action == :tokens
{
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
end
- def url(_post, action, authorization)
- if %i[authorize purchase credit].include?(action)
+ def tokenize(payment_method, options = {})
+ post = {}
+ add_authorization_type(post, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ commit(:tokens, post[:source])
+ end
+
+ def url(action, authorization)
+ case action
+ when :authorize, :purchase, :credit
"#{base_url}/payments"
- elsif action == :capture
+ when :unstore, :store
+ "#{base_url}/instruments/#{authorization}"
+ when :capture
"#{base_url}/payments/#{authorization}/captures"
- elsif action == :refund
+ when :refund
"#{base_url}/payments/#{authorization}/refunds"
- elsif action == :void
+ when :void
"#{base_url}/payments/#{authorization}/voids"
- elsif action == :incremental_authorize
+ when :incremental_authorize
"#{base_url}/payments/#{authorization}/authorizations"
+ when :tokens
+ "#{base_url}/tokens"
+ when :verify_payment
+ "#{base_url}/payments/#{authorization}"
else
"#{base_url}/payments/#{authorization}/#{action}"
end
@@ -363,7 +420,12 @@ def parse(body, error: nil)
def success_from(action, response)
return response['status'] == 'Pending' if action == :credit
+ return true if action == :unstore && response == 204
+ store_response = response['token'] || response['id']
+ if store_response
+ return true if (action == :tokens && store_response.match(/tok/)) || (action == :store && store_response.match(/src_/))
+ end
response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id')
end
@@ -416,6 +478,16 @@ def token_type_from(payment_method)
'applepay'
end
end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code after unstore(delete instrument), because the body is nil
+ when 200...300
+ response.body || response.code
+ else
+ raise ResponseError.new(response)
+ end
+ end
end
end
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 82237b04bf9..2a9b3103e69 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -198,6 +198,10 @@ checkout_v2:
client_id: CLIENT_ID_FOR_OAUTH_TRANSACTIONS
client_secret: CLIENT_SECRET_FOR_OAUTH_TRANSACTIONS
+checkout_v2_token:
+ secret_key: sk_sbox_xxxxxxxxxxxxxxxxx
+ public_key: pk_sbox_xxxxxxxxxxxxxxxxx
+
citrus_pay:
userid: CPF00001
password: 7c70414732de7e0ba3a04db5f24fcec8
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 4a212d8790c..2275c51bfc1 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -3,20 +3,22 @@
class RemoteCheckoutV2Test < Test::Unit::TestCase
def setup
gateway_fixtures = fixtures(:checkout_v2)
+ gateway_token_fixtures = fixtures(:checkout_v2_token)
@gateway = CheckoutV2Gateway.new(secret_key: gateway_fixtures[:secret_key])
@gateway_oauth = CheckoutV2Gateway.new({ client_id: gateway_fixtures[:client_id], client_secret: gateway_fixtures[:client_secret] })
+ @gateway_token = CheckoutV2Gateway.new(secret_key: gateway_token_fixtures[:secret_key], public_key: gateway_token_fixtures[:public_key])
@amount = 200
- @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025')
+ @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: Time.now.year + 1)
@expired_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2010')
- @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: '2025')
- @threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: '2020')
+ @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: Time.now.year + 1)
+ @threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: Time.now.year + 1)
@mada_card = credit_card('5043000000000000', brand: 'mada')
@vts_network_token = network_tokenization_credit_card('4242424242424242',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :network_token,
brand: 'visa',
verification_value: nil)
@@ -25,7 +27,7 @@ def setup
eci: '02',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :network_token,
brand: 'master',
verification_value: nil)
@@ -34,21 +36,21 @@ def setup
eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :google_pay,
verification_value: nil)
@google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card('5436031030606378',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :google_pay,
brand: 'master',
verification_value: nil)
@google_pay_pan_only_network_token = network_tokenization_credit_card('4242424242424242',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :google_pay,
verification_value: nil)
@@ -56,7 +58,7 @@ def setup
eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
month: '10',
- year: '2025',
+ year: Time.now.year + 1,
source: :apple_pay,
verification_value: nil)
@@ -134,6 +136,16 @@ def test_network_transaction_scrubbing
assert_scrubbed(@gateway.options[:secret_key], transcript)
end
+ def test_store_transcript_scrubbing
+ response = nil
+ transcript = capture_transcript(@gateway) do
+ response = @gateway_token.store(@credit_card, @options)
+ end
+ token = response.responses.first.params['token']
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(token, transcript)
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -583,6 +595,92 @@ def test_successful_credit
assert_equal 'Succeeded', response.message
end
+ def test_successful_store
+ response = @gateway_token.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_unstore_after_store
+ store = @gateway_token.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Succeeded', store.message
+ source_id = store.params['id']
+ response = @gateway_token.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_unstore_after_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_purchase_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ end
+
+ def test_successful_store_apple_pay
+ response = @gateway.store(@apple_pay_network_token, @options)
+ assert_success response
+ end
+
+ def test_successful_unstore_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ end
+
+ def test_success_store_with_google_pay_3ds
+ response = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
+ assert_success response
+ end
+
+ def test_failed_store_oauth_credit_card
+ response = @gateway_oauth.store(@credit_card, @options)
+ assert_failure response
+ assert_equal '401: Unauthorized', response.message
+ end
+
+ def test_successful_purchase_oauth_after_store_credit_card
+ store = @gateway_token.store(@credit_card, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway_oauth.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_after_store_with_google_pay
+ store = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_after_store_with_apple_pay
+ store = @gateway.store(@apple_pay_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_success_purchase_oauth_after_store_ouath_with_apple_pay
+ store = @gateway_oauth.store(@apple_pay_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway_oauth.purchase(@amount, token, @options)
+ assert_success response
+ end
+
def test_successful_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -648,6 +746,15 @@ def test_successful_void
assert_success void
end
+ def test_successful_purchase_store_after_verify
+ verify = @gateway.verify(@apple_pay_network_token, @options)
+ assert_success verify
+ source_id = verify.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ assert_success verify
+ end
+
def test_successful_void_via_oauth
auth = @gateway_oauth.authorize(@amount, @credit_card, @options)
assert_success auth
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 6a04bfbcbca..18c0734432a 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -18,13 +18,16 @@ def setup
secret_key: '1111111111111'
)
@gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
-
+ @gateway_api = CheckoutV2Gateway.new({
+ secret_key: '1111111111111',
+ public_key: '2222222222222'
+ })
@credit_card = credit_card
@amount = 100
end
def test_successful_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -34,7 +37,7 @@ def test_successful_purchase
end
def test_successful_purchase_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -45,7 +48,7 @@ def test_successful_purchase_includes_avs_result
end
def test_successful_purchase_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -57,9 +60,9 @@ def test_successful_purchase_using_vts_network_token_without_eci
'4242424242424242',
{ source: :network_token, brand: 'visa' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -75,18 +78,18 @@ def test_successful_purchase_using_vts_network_token_without_eci
end
def test_successful_passing_processing_channel_id
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { processing_channel_id: '123456abcde' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['processing_channel_id'], '123456abcde')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_incremental_authorization
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card, { incremental_authorization: 'abcd1234' })
- end.check_request do |endpoint, _data, _headers|
+ end.check_request do |_method, endpoint, _data, _headers|
assert_include endpoint, 'abcd1234'
end.respond_with(successful_incremental_authorize_response)
@@ -94,18 +97,18 @@ def test_successful_passing_incremental_authorization
end
def test_successful_passing_authorization_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { authorization_type: 'Estimated' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['authorization_type'], 'Estimated')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_exemption_and_challenge_indicator
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['3ds']['exemption'], 'no_preference')
assert_equal(request_data['3ds']['challenge_indicator'], 'trusted_listing')
@@ -113,9 +116,9 @@ def test_successful_passing_exemption_and_challenge_indicator
end
def test_successful_passing_capture_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, 'abc', { capture_type: 'NonFinal' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['capture_type'], 'NonFinal')
end.respond_with(successful_capture_response)
@@ -126,9 +129,9 @@ def test_successful_purchase_using_vts_network_token_with_eci
'4242424242424242',
{ source: :network_token, brand: 'visa', eci: '06' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -148,9 +151,9 @@ def test_successful_purchase_using_mdes_network_token
'5436031030606378',
{ source: :network_token, brand: 'master' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -170,9 +173,9 @@ def test_successful_purchase_using_apple_pay_network_token
'4242424242424242',
{ source: :apple_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -192,9 +195,9 @@ def test_successful_purchase_using_android_pay_network_token
'4242424242424242',
{ source: :android_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -214,9 +217,9 @@ def test_successful_purchase_using_google_pay_network_token
'4242424242424242',
{ source: :google_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -236,9 +239,9 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
'4242424242424242',
{ source: :google_pay }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -266,7 +269,7 @@ def test_successful_render_for_oauth
end
def test_successful_authorize_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -277,7 +280,7 @@ def test_successful_authorize_includes_avs_result
end
def test_successful_authorize_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -285,9 +288,9 @@ def test_successful_authorize_includes_cvv_result
end
def test_purchase_with_additional_fields
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { descriptor_city: 'london', descriptor_name: 'sherlock' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(/"billing_descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data)
end.respond_with(successful_purchase_response)
@@ -297,16 +300,16 @@ def test_purchase_with_additional_fields
def test_successful_purchase_passing_metadata_with_mada_card_type
@credit_card.brand = 'mada'
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['metadata']['udf1'], 'mada')
end.respond_with(successful_purchase_response)
end
def test_failed_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(failed_purchase_response)
assert_failure response
@@ -314,14 +317,14 @@ def test_failed_purchase
end
def test_successful_authorize_and_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -329,7 +332,7 @@ def test_successful_authorize_and_capture
end
def test_successful_authorize_and_capture_with_additional_options
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
card_on_file: true,
transaction_indicator: 2,
@@ -340,7 +343,7 @@ def test_successful_authorize_and_capture_with_additional_options
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"stored":"true"}, data)
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"previous_payment_id":"pay_123"}, data)
@@ -351,7 +354,7 @@ def test_successful_authorize_and_capture_with_additional_options
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -359,7 +362,7 @@ def test_successful_authorize_and_capture_with_additional_options
end
def test_successful_purchase_with_stored_credentials
- initial_response = stub_comms do
+ initial_response = stub_comms(@gateway, :ssl_request) do
initial_options = {
stored_credential: {
initial_transaction: true,
@@ -367,7 +370,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, initial_options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"merchant_initiated":false}, data)
end.respond_with(successful_purchase_initial_stored_credential_response)
@@ -376,7 +379,7 @@ def test_successful_purchase_with_stored_credentials
assert_equal 'pay_7jcf4ovmwnqedhtldca3fjli2y', initial_response.params['id']
network_transaction_id = initial_response.params['id']
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false,
@@ -385,7 +388,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -396,7 +399,7 @@ def test_successful_purchase_with_stored_credentials
end
def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false
@@ -404,7 +407,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
merchant_initiated_transaction_id: 'pay_7jcf4ovmwnqedhtldca3fjli2y'
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -415,7 +418,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
end
def test_successful_purchase_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -423,7 +426,7 @@ def test_successful_purchase_with_metadata
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_using_stored_credential_response)
@@ -432,7 +435,7 @@ def test_successful_purchase_with_metadata
end
def test_successful_authorize_and_capture_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -440,7 +443,7 @@ def test_successful_authorize_and_capture_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -448,7 +451,7 @@ def test_successful_authorize_and_capture_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -456,14 +459,14 @@ def test_successful_authorize_and_capture_with_metadata
end
def test_moto_transaction_is_properly_set
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
manual_entry: true
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"MOTO"}, data)
end.respond_with(successful_authorize_response)
@@ -471,13 +474,13 @@ def test_moto_transaction_is_properly_set
end
def test_3ds_passed
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
callback_url: 'https://www.example.com'
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"success_url"}, data)
assert_match(%r{"failure_url"}, data)
end.respond_with(successful_authorize_response)
@@ -489,7 +492,16 @@ def test_successful_verify_payment
response = stub_comms(@gateway, :ssl_request) do
@gateway.verify_payment('testValue')
end.respond_with(successful_verify_payment_response)
+ assert_success response
+ end
+ def test_verify_payment_request
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.verify_payment('testValue')
+ end.check_request do |_method, endpoint, data, _headers|
+ assert_equal nil, data
+ assert_equal 'https://api.sandbox.checkout.com/payments/testValue', endpoint
+ end.respond_with(successful_verify_payment_response)
assert_success response
end
@@ -502,7 +514,7 @@ def test_failed_verify_payment
end
def test_successful_authorize_and_capture_with_3ds
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
attempt_n3d: true,
@@ -520,7 +532,7 @@ def test_successful_authorize_and_capture_with_3ds
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -528,7 +540,7 @@ def test_successful_authorize_and_capture_with_3ds
end
def test_successful_authorize_and_capture_with_3ds2
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
three_d_secure: {
@@ -545,7 +557,7 @@ def test_successful_authorize_and_capture_with_3ds2
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -553,7 +565,7 @@ def test_successful_authorize_and_capture_with_3ds2
end
def test_failed_authorize
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(failed_authorize_response)
@@ -563,7 +575,7 @@ def test_failed_authorize
end
def test_failed_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.capture(100, '')
end.respond_with(failed_capture_response)
@@ -571,14 +583,14 @@ def test_failed_capture
end
def test_successful_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms do
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -586,7 +598,7 @@ def test_successful_void
end
def test_successful_void_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -594,7 +606,7 @@ def test_successful_void_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -602,7 +614,7 @@ def test_successful_void_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms do
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -610,7 +622,7 @@ def test_successful_void_with_metadata
end
def test_failed_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.void('5d53a33d960c46d00f5dc061947d998c')
end.respond_with(failed_void_response)
assert_failure response
@@ -623,9 +635,9 @@ def test_successfully_passes_fund_type_and_fields
source_id: 'ca_spwmped4qmqenai7hcghquqle4',
account_holder_type: 'individual'
}
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.credit(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['instruction']['funds_transfer_type'], options[:funds_transfer_type]
assert_equal request['source']['type'], options[:source_type]
@@ -638,14 +650,14 @@ def test_successfully_passes_fund_type_and_fields
end
def test_successful_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -653,7 +665,7 @@ def test_successful_refund
end
def test_successful_refund_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -661,7 +673,7 @@ def test_successful_refund_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_response)
@@ -669,7 +681,7 @@ def test_successful_refund_with_metadata
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -677,7 +689,7 @@ def test_successful_refund_with_metadata
end
def test_failed_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.refund(nil, '')
end.respond_with(failed_refund_response)
@@ -685,7 +697,7 @@ def test_failed_refund
end
def test_successful_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(successful_verify_response)
assert_success response
@@ -693,13 +705,40 @@ def test_successful_verify
end
def test_failed_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(failed_verify_response)
assert_failure response
assert_equal 'request_invalid: card_number_invalid', response.message
end
+ def test_successful_store
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card)
+ end.check_request do |_method, endpoint, data, _headers|
+ if /tokens/.match?(endpoint)
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ elsif /instruments/.match?(endpoint)
+ assert_match(%r{"type":"token"}, data)
+ assert_match(%r{"token":"tok_}, data)
+ end
+ end.respond_with(succesful_token_response, succesful_store_response)
+ end
+
+ def test_successful_tokenize
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.send(:tokenize, @credit_card)
+ end.check_request do |_action, endpoint, data, _headers|
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ end.respond_with(succesful_token_response)
+ end
+
def test_transcript_scrubbing
assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed)
end
@@ -709,7 +748,7 @@ def test_network_transaction_scrubbing
end
def test_invalid_json
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(invalid_json_response)
@@ -718,7 +757,7 @@ def test_invalid_json
end
def test_error_code_returned
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(error_code_response)
@@ -727,7 +766,7 @@ def test_error_code_returned
end
def test_4xx_error_message
- @gateway.expects(:ssl_post).raises(error_4xx_response)
+ @gateway.expects(:ssl_request).raises(error_4xx_response)
assert response = @gateway.purchase(@amount, @credit_card)
@@ -775,6 +814,12 @@ def successful_purchase_response
)
end
+ def succesful_store_response
+ %(
+ {"id":"src_vzzqipykt5ke5odazx5d7nikii","type":"card","fingerprint":"9F3BAD2E48C6C8579F2F5DC0710B7C11A8ACD5072C3363A72579A6FB227D64BE","expiry_month":6,"expiry_year":2025,"scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","customer":{"id":"cus_gmthnluatgounpoiyzbmn5fvua", "email":"longbob.longsen@example.com"}}
+ )
+ end
+
def successful_purchase_with_network_token_response
purchase_response = JSON.parse(successful_purchase_response)
purchase_response['source']['payment_account_reference'] = '2FCFE326D92D4C27EDD699560F484'
@@ -1035,6 +1080,10 @@ def successful_verify_payment_response
)
end
+ def succesful_token_response
+ %({"type":"card","token":"tok_267wy4hwrpietkmbbp5iswwhvm","expires_on":"2023-01-03T20:18:49.0006481Z","expiry_month":6,"expiry_year":2025,"name":"Longbob Longsen","scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic"})
+ end
+
def failed_verify_payment_response
%(
{"id":"pay_xrwmaqlar73uhjtyoghc7bspa4","requested_on":"2019-08-14T18:32:50Z","source":{"type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"7863","fingerprint":"DC20145B78E242C561A892B83CB64471729D7A5063E5A5B341035713B8FDEC92","bin":"453962"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"EuyOZtgt8KI4tolEH8lqxCclWqz","status":"Declined","approved":false,"3ds":{"downgraded":false,"enrolled":"Y","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_bb4b7eu35sde7o33fq2xchv7oq","name":"Jane Doe"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4/actions"}}}
From cdaa2f6b580f72382662c19fb3bdd524211a77f2 Mon Sep 17 00:00:00 2001
From: Luis
Date: Fri, 10 Feb 2023 15:27:32 -0500
Subject: [PATCH 022/390] CybersourceREST - Refund | Credit
Description
-------------------------
This integration support the following payment operations:
- Refund
- Credit
Closes #4700
Unit test
-------------------------
Finished in 40.91494 seconds.
5454 tests, 77134 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
133.30 tests/s, 1885.23 assertions/s
Rubocop
-------------------------
760 files inspected, no offenses detected
GWI-471
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 44 ++++++++++--
.../gateways/remote_cyber_source_rest_test.rb | 71 +++++++++++++++++++
test/unit/gateways/cyber_source_rest_test.rb | 2 +-
4 files changed, 113 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 6c533693fd0..22c41ea60e1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -69,6 +69,7 @@
* CommerceHub: Fixing verify status and prevent tokenization [heavyblade] #4716
* Payeezy: Update Stored Credentials [almalee24] #4711
* CheckoutV2: Add store/unstore [gasb150] #4712
+* CybersourceREST - Refund | Credit [sinourain] #4700
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index a4f3b62747d..bace0b3f1ae 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -44,7 +44,17 @@ def authorize(money, payment, options = {}, capture = false)
post = build_auth_request(money, payment, options)
post[:processingInformation] = { capture: true } if capture
- commit('/pts/v2/payments/', post)
+ commit('payments', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = build_refund_request(money, options)
+ commit("payments/#{authorization}/refunds", post)
+ end
+
+ def credit(money, payment, options = {})
+ post = build_credit_request(money, payment, options)
+ commit('credits', post)
end
def supports_scrubbing?
@@ -73,6 +83,23 @@ def build_auth_request(amount, payment, options)
end.compact
end
+ def build_refund_request(amount, options)
+ { clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
+ add_code(post, options)
+ add_amount(post, amount)
+ end.compact
+ end
+
+ def build_credit_request(amount, payment, options)
+ { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
+ add_code(post, options)
+ add_credit_card(post, payment)
+ add_amount(post, amount)
+ add_address(post, payment, options[:billing_address], options, :billTo)
+ add_merchant_description(post, options)
+ end.compact
+ end
+
def add_code(post, options)
return unless options[:order_id].present?
@@ -129,8 +156,17 @@ def add_address(post, payment_method, address, options, address_type)
}.compact
end
+ def add_merchant_description(post, options)
+ return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
+
+ merchant = post[:merchantInformation][:merchantDescriptor] = {}
+ merchant[:name] = options[:merchant_descriptor_name] if options[:merchant_descriptor_name]
+ merchant[:address1] = options[:merchant_descriptor_address1] if options[:merchant_descriptor_address1]
+ merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality]
+ end
+
def url(action)
- "#{(test? ? test_url : live_url)}#{action}"
+ "#{(test? ? test_url : live_url)}/pts/v2/#{action}"
end
def host
@@ -160,7 +196,7 @@ def commit(action, post)
end
def success_from(response)
- response['status'] == 'AUTHORIZED'
+ %w(AUTHORIZED PENDING).include?(response['status'])
end
def message_from(response)
@@ -183,7 +219,7 @@ def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Tim
string_to_sign = {
host: host,
date: gmtdatetime,
- "(request-target)": "#{http_method} #{resource}",
+ "(request-target)": "#{http_method} /pts/v2/#{resource}",
digest: digest,
"v-c-merchant-id": @options[:merchant_id]
}.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 06b688a3a2e..a692461d5fc 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -70,6 +70,77 @@ def test_successful_purchase
assert_nil response.params['_links']['capture']
end
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert response.params['_links']['void'].present?
+ end
+
+ def test_failure_refund
+ purchase = @gateway.purchase(@amount, @card_without_funds, @options)
+ response = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_failure response
+ assert response.test?
+ assert_match %r{Declined - One or more fields in the request contains invalid data}, response.params['message']
+ assert_equal 'INVALID_DATA', response.params['reason']
+ end
+
+ def test_successful_partial_refund
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response = @gateway.refund(@amount / 2, purchase.authorization, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert response.params['_links']['void'].present?
+ end
+
+ def test_successful_repeat_refund_transaction
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response1 = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_success response1
+ assert response1.test?
+ assert_equal 'PENDING', response1.message
+ assert response1.params['id'].present?
+ assert response1.params['_links']['void']
+
+ response2 = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success response2
+ assert response2.test?
+ assert_equal 'PENDING', response2.message
+ assert response2.params['id'].present?
+ assert response2.params['_links']['void']
+
+ assert_not_equal response1.params['_links']['void'], response2.params['_links']['void']
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_failure_credit
+ response = @gateway.credit(@amount, @card_without_funds, @options)
+
+ assert_failure response
+ assert response.test?
+ assert_match %r{Decline - Invalid account number}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.authorize(@amount, @visa_card, @options)
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 70258beb9d2..326f820883c 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -149,7 +149,7 @@ def test_add_shipping_address
end
def test_url_building
- assert_equal "#{@gateway.class.test_url}/action", @gateway.send(:url, '/action')
+ assert_equal "#{@gateway.class.test_url}/pts/v2/action", @gateway.send(:url, 'action')
end
private
From 539c9cf2656b77b8d2cf8fc42e4cf9e3c796208b Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Wed, 1 Mar 2023 11:13:18 -0500
Subject: [PATCH 023/390] Payeezy: Ignore `xid` for AP Amex (#4721)
Payeezy has stated the inclusion of `xid` values for AP (Amex
underlying) transactions could result in failures. Add guard to ignore
adding this field if the underlying is `american_express`
Unit: 49 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 47 tests, 186 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.../billing/gateways/payeezy.rb | 2 +-
test/remote/gateways/remote_payeezy_test.rb | 15 ++++++++++++
test/unit/gateways/payeezy_test.rb | 24 +++++++++++++++++--
3 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index 8906ab0a606..b724cf3a14a 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -247,7 +247,7 @@ def add_network_tokenization(params, payment_method, options)
nt_card[:card_number] = payment_method.number
nt_card[:exp_date] = format_exp_date(payment_method.month, payment_method.year)
nt_card[:cvv] = payment_method.verification_value
- nt_card[:xid] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty?
+ nt_card[:xid] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty? || payment_method.brand.include?('american_express')
nt_card[:cavv] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty?
nt_card[:wallet_provider_id] = 'APPLE_PAY'
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
index fc9b60877e4..8597009759b 100644
--- a/test/remote/gateways/remote_payeezy_test.rb
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -50,6 +50,16 @@ def setup
source: :apple_pay,
verification_value: 569
)
+ @apple_pay_card_amex = network_tokenization_credit_card(
+ '373953192351004',
+ brand: 'american_express',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ eci: 5,
+ source: :apple_pay,
+ verification_value: 569
+ )
end
def test_successful_store
@@ -86,6 +96,11 @@ def test_successful_purchase_with_apple_pay
assert_success response
end
+ def test_successful_purchase_with_apple_pay_amex
+ assert response = @gateway.purchase(@amount, @apple_pay_card_amex, @options)
+ assert_success response
+ end
+
def test_successful_authorize_and_capture_with_apple_pay
assert auth = @gateway.authorize(@amount, @apple_pay_card, @options)
assert_success auth
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index a5ba8f063a3..dfbda5f591a 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -55,6 +55,16 @@ def setup
source: :apple_pay,
verification_value: 569
)
+ @apple_pay_card_amex = network_tokenization_credit_card(
+ '373953192351004',
+ brand: 'american_express',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ eci: 5,
+ source: :apple_pay,
+ verification_value: 569
+ )
end
def test_invalid_credentials
@@ -115,8 +125,18 @@ def test_successful_purchase_with_apple_pay_no_cryptogram
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['eci_indicator'], '5'
- assert_nil request['xid']
- assert_nil request['cavv']
+ assert_nil request['3DS']['xid']
+ assert_nil request['3DS']['cavv']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_purchase_with_apple_pay_amex
+ stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card_amex, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert request['3DS']['cavv'], @apple_pay_card_amex.payment_cryptogram
+ assert_nil request['3DS']['xid']
end.respond_with(successful_purchase_response)
end
From 77684846f31072178e2815a2f22b037deb1f79fe Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 1 Mar 2023 13:16:06 -0500
Subject: [PATCH 024/390] TrustCommerce Verify feature added (#4699)
Enable verify feature on TrustCommerce Gateway
Remote tests
--------------------------------------------------------------------------
21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications 85.7143% passed
Failing tests not related with changes
unit tests
--------------------------------------------------------------------------
22 tests, 72 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications 100% passed
---
lib/active_merchant/billing/gateways/trust_commerce.rb | 5 +++--
test/remote/gateways/remote_trust_commerce_test.rb | 7 +++++++
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index f7ad38c67ff..e2a3b7fbc3e 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -249,8 +249,9 @@ def void(authorization, options = {})
end
def verify(credit_card, options = {})
- add_creditcard(options, credit_card)
- commit('verify', options)
+ parameters = {}
+ add_creditcard(parameters, credit_card)
+ commit('verify', parameters)
end
# recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb
index 502f0ed30bd..19e1564c649 100644
--- a/test/remote/gateways/remote_trust_commerce_test.rb
+++ b/test/remote/gateways/remote_trust_commerce_test.rb
@@ -211,6 +211,13 @@ def test_successful_verify
assert_success response
end
+ def test_failed_verify_with_invalid_card
+ assert response = @gateway.verify(@declined_credit_card)
+ assert_equal 'baddata', response.params['status']
+ assert_match %r{A field was improperly formatted}, response.message
+ assert_failure response
+ end
+
def test_successful_recurring
assert response = @gateway.recurring(@amount, @credit_card, periodicity: :weekly)
From 546fea797ab1530196717cabc899fa77bb980905 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 22 Feb 2023 15:25:31 -0800
Subject: [PATCH 025/390] CER-440
Add papypal_custom_field and paypal_description gateway specific fields to braintree_blue gateway.
Local:
5455 tests, 77085 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications
99.6517% passed
Unit:
94 tests, 207 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
101 tests, 493 assertions, 4 failures, 4 errors, 0 pendings, 0 omissions, 0 notifications
92.0792% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/braintree_blue.rb | 10 ++++++++++
test/remote/gateways/remote_braintree_blue_test.rb | 9 +++++++++
test/unit/gateways/braintree_blue_test.rb | 9 +++++++++
4 files changed, 29 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 22c41ea60e1..41b853ba6af 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -70,6 +70,7 @@
* Payeezy: Update Stored Credentials [almalee24] #4711
* CheckoutV2: Add store/unstore [gasb150] #4712
* CybersourceREST - Refund | Credit [sinourain] #4700
+* Braintree - Add Paypal custom fields [yunnydang] #4713
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 146470d66ba..c8cf471b016 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -652,6 +652,7 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
add_descriptor(parameters, options)
add_risk_data(parameters, options)
+ add_paypal_options(parameters, options)
add_travel_data(parameters, options) if options[:travel_data]
add_lodging_data(parameters, options) if options[:lodging_data]
add_channel(parameters, options)
@@ -728,6 +729,15 @@ def add_risk_data(parameters, options)
}
end
+ def add_paypal_options(parameters, options)
+ return unless options[:paypal_custom_field] || options[:paypal_description]
+
+ parameters[:options][:paypal] = {
+ custom_field: options[:paypal_custom_field],
+ description: options[:paypal_description]
+ }
+ end
+
def add_level_2_data(parameters, options)
parameters[:tax_amount] = options[:tax_amount] if options[:tax_amount]
parameters[:tax_exempt] = options[:tax_exempt] if options[:tax_exempt]
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 842218143e1..758df320d74 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -201,6 +201,15 @@ def test_successful_purchase_sending_risk_data
assert_success response
end
+ def test_successful_purchase_with_paypal_options
+ options = @options.merge(
+ paypal_custom_field: 'abc',
+ paypal_description: 'shoes'
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
# Follow instructions found at https://developer.paypal.com/braintree/articles/guides/payment-methods/venmo#multiple-profiles
# for sandbox control panel https://sandbox.braintreegateway.com/login to create a venmo profile.
# Insert your Profile Id into fixtures.
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index af00505e227..3d763d7abfb 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -264,6 +264,15 @@ def test_hold_in_escrow_can_be_specified
@gateway.authorize(100, credit_card('41111111111111111111'), hold_in_escrow: true)
end
+ def test_paypal_options_can_be_specified
+ Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
+ (params[:options][:paypal][:custom_field] == 'abc')
+ (params[:options][:paypal][:description] == 'shoes')
+ end.returns(braintree_result)
+
+ @gateway.authorize(100, credit_card('4111111111111111'), paypal_custom_field: 'abc', paypal_description: 'shoes')
+ end
+
def test_merchant_account_id_absent_if_not_provided
Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
not params.has_key?(:merchant_account_id)
From 2daf48bcaf3b2734e0bd3b4b593d30292309c9f0 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 27 Feb 2023 12:02:55 -0800
Subject: [PATCH 026/390] CER-460 Add descriptor phone number to blue_snap
Local:
5457 tests, 77095 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications
99.6518% passed
Unit:
45 tests, 269 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
53 tests, 171 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
83.0189% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/blue_snap.rb | 2 ++
test/remote/gateways/remote_blue_snap_test.rb | 7 +++++++
test/unit/gateways/blue_snap_test.rb | 13 +++++++++++++
4 files changed, 23 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 41b853ba6af..5b98ae2fa6c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -71,6 +71,7 @@
* CheckoutV2: Add store/unstore [gasb150] #4712
* CybersourceREST - Refund | Credit [sinourain] #4700
* Braintree - Add Paypal custom fields [yunnydang] #4713
+* BlueSnap - Add descriptor phone number field [yunnydang] #4717
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/blue_snap.rb b/lib/active_merchant/billing/gateways/blue_snap.rb
index de2ec414d5c..5abac55c16b 100644
--- a/lib/active_merchant/billing/gateways/blue_snap.rb
+++ b/lib/active_merchant/billing/gateways/blue_snap.rb
@@ -260,6 +260,7 @@ def add_metadata(doc, options)
def add_order(doc, options)
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
add_metadata(doc, options)
add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
add_level_3_data(doc, options)
@@ -366,6 +367,7 @@ def add_shipping_contact_info(doc, payment_method, options)
def add_alt_transaction_purchase(doc, money, payment_method_details, options)
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
add_amount(doc, money, options)
vaulted_shopper_id = payment_method_details.vaulted_shopper_id
diff --git a/test/remote/gateways/remote_blue_snap_test.rb b/test/remote/gateways/remote_blue_snap_test.rb
index 3eb143e6986..a984beeeb1c 100644
--- a/test/remote/gateways/remote_blue_snap_test.rb
+++ b/test/remote/gateways/remote_blue_snap_test.rb
@@ -447,6 +447,13 @@ def test_successful_authorize_and_partial_capture
assert_equal 'Success', capture.message
end
+ def test_successful_authorize_with_descriptor_phone_number
+ response = @gateway.authorize(@amount, @credit_card, @options.merge({ descriptor_phone_number: '321-321-4321' }))
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
def test_successful_authorize_and_capture_with_3ds2_auth
auth = @gateway.authorize(@amount, @three_ds_master_card, @options_3ds2)
assert_success auth
diff --git a/test/unit/gateways/blue_snap_test.rb b/test/unit/gateways/blue_snap_test.rb
index 8f592125771..8553f4e9aea 100644
--- a/test/unit/gateways/blue_snap_test.rb
+++ b/test/unit/gateways/blue_snap_test.rb
@@ -343,6 +343,19 @@ def test_successful_authorize
assert_equal '1012082893', response.authorization
end
+ def test_successful_authorize_with_descriptor_phone_number
+ options_with_phone_number = {
+ descriptor_phone_number: '321-321-4321'
+ }
+ response = stub_comms(@gateway, :raw_ssl_request) do
+ @gateway.authorize(@amount, @credit_card, options_with_phone_number)
+ end.check_request do |_method, _url, data|
+ assert_match('321-321-4321', data)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ end
+
def test_successful_authorize_with_3ds_auth
response = stub_comms(@gateway, :raw_ssl_request) do
@gateway.authorize(@amount, @credit_card, @options_3ds2)
From 36848079303be947d054cb08e78e8d8856402cc8 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Tue, 28 Feb 2023 19:26:02 -0800
Subject: [PATCH 027/390] Braintree: Update transaction hash method
This PR is to update the transaction method to include the processor_authorization_code.
---
CHANGELOG | 1 +
.../billing/gateways/braintree_blue.rb | 27 ++++++++++---------
.../gateways/remote_braintree_blue_test.rb | 7 +++++
3 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5b98ae2fa6c..9fdc38b8611 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -72,6 +72,7 @@
* CybersourceREST - Refund | Credit [sinourain] #4700
* Braintree - Add Paypal custom fields [yunnydang] #4713
* BlueSnap - Add descriptor phone number field [yunnydang] #4717
+* Braintree - Update transaction hash to include processor_authorization_code [yunnydang] #4718
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index c8cf471b016..6e1b4f00adf 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -604,19 +604,20 @@ def transaction_hash(result)
end
{
- 'order_id' => transaction.order_id,
- 'amount' => transaction.amount.to_s,
- 'status' => transaction.status,
- 'credit_card_details' => credit_card_details,
- 'customer_details' => customer_details,
- 'billing_details' => billing_details,
- 'shipping_details' => shipping_details,
- 'vault_customer' => vault_customer,
- 'merchant_account_id' => transaction.merchant_account_id,
- 'risk_data' => risk_data,
- 'network_transaction_id' => transaction.network_transaction_id || nil,
- 'processor_response_code' => response_code_from_result(result),
- 'recurring' => transaction.recurring
+ 'order_id' => transaction.order_id,
+ 'amount' => transaction.amount.to_s,
+ 'status' => transaction.status,
+ 'credit_card_details' => credit_card_details,
+ 'customer_details' => customer_details,
+ 'billing_details' => billing_details,
+ 'shipping_details' => shipping_details,
+ 'vault_customer' => vault_customer,
+ 'merchant_account_id' => transaction.merchant_account_id,
+ 'risk_data' => risk_data,
+ 'network_transaction_id' => transaction.network_transaction_id || nil,
+ 'processor_response_code' => response_code_from_result(result),
+ 'processor_authorization_code' => transaction.processor_authorization_code,
+ 'recurring' => transaction.recurring
}
end
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 758df320d74..f97f6a73d1f 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -1191,6 +1191,13 @@ def test_successful_purchase_with_the_same_bank_account_several_times
assert_equal '4002 Settlement Pending', response.message
end
+ def test_successful_purchase_with_processor_authorization_code
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['processor_authorization_code']
+ end
+
def test_unsucessful_purchase_using_a_bank_account_token_not_verified
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
response = @gateway.store(bank_account, @options.merge(@check_required_options))
From 0e27bce82332df2d5176321bc5aec63797fc1661 Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Wed, 1 Mar 2023 14:18:02 -0500
Subject: [PATCH 028/390] CyberSourceRest: Add apple pay, google pay
Summary:
-----------------------
In order to perform ApplePay and GooglePay
transaction this commit, adds support.
Closes #4708
Remote test
-----------------------
Finished in 7.216327 seconds.
18 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit test
-----------------------
Finished in 0.032725 seconds.
15 tests, 80 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop
-----------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 40 ++++++-
.../gateways/remote_cyber_source_rest_test.rb | 102 ++++++++++++++++++
test/unit/gateways/cyber_source_rest_test.rb | 72 +++++++++++++
4 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9fdc38b8611..0bf4787eacc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -73,6 +73,7 @@
* Braintree - Add Paypal custom fields [yunnydang] #4713
* BlueSnap - Add descriptor phone number field [yunnydang] #4717
* Braintree - Update transaction hash to include processor_authorization_code [yunnydang] #4718
+* CyberSourceRest: Add apple pay, google pay [gasb150] #4708
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index bace0b3f1ae..53a4e73ba3b 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -30,6 +30,10 @@ class CyberSourceRestGateway < Gateway
unionpay: '062',
visa: '001'
}
+ PAYMENT_SOLUTION = {
+ apple_pay: '001',
+ google_pay: '012'
+ }
def initialize(options = {})
requires!(options, :merchant_id, :public_key, :private_key)
@@ -42,7 +46,7 @@ def purchase(money, payment, options = {})
def authorize(money, payment, options = {}, capture = false)
post = build_auth_request(money, payment, options)
- post[:processingInformation] = { capture: true } if capture
+ post[:processingInformation][:capture] = true if capture
commit('payments', post)
end
@@ -76,7 +80,7 @@ def build_auth_request(amount, payment, options)
{ clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
add_customer_id(post, options)
add_code(post, options)
- add_credit_card(post, payment)
+ add_payment(post, payment, options)
add_amount(post, amount)
add_address(post, payment, options[:billing_address], options, :billTo)
add_address(post, payment, options[:shipping_address], options, :shipTo)
@@ -121,6 +125,38 @@ def add_amount(post, amount)
}
end
+ def add_payment(post, payment, options)
+ post[:processingInformation] = {}
+ if payment.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_card(post, payment, options)
+ else
+ add_credit_card(post, payment)
+ end
+ end
+
+ def add_network_tokenization_card(post, payment, options)
+ post[:processingInformation][:paymentSolution] = PAYMENT_SOLUTION[payment.source]
+ post[:processingInformation][:commerceIndicator] = 'internet' unless card_brand(payment) == 'jcb'
+
+ post[:paymentInformation][:tokenizedCard] = {
+ number: payment.number,
+ expirationMonth: payment.month,
+ expirationYear: payment.year,
+ cryptogram: payment.payment_cryptogram,
+ transactionType: '1',
+ type: CREDIT_CARD_CODES[card_brand(payment).to_sym]
+ }
+
+ if card_brand(payment) == 'master'
+ post[:consumerAuthenticationInformation] = {
+ ucafAuthenticationData: payment.payment_cryptogram,
+ ucafCollectionIndicator: '2'
+ }
+ else
+ post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram }
+ end
+ end
+
def add_credit_card(post, creditcard)
post[:paymentInformation][:card] = {
number: creditcard.number,
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index a692461d5fc..11ba82a0913 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -10,6 +10,64 @@ def setup
month: 12,
year: 2031)
+ @apple_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569
+ )
+
+ @google_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569
+ )
+
+ @google_pay_master = network_tokenization_credit_card(
+ '5555555555554444',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'master'
+ )
+
+ @apple_pay_jcb = network_tokenization_credit_card(
+ '3566111111111113',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'jcb'
+ )
+
+ @apple_pay_american_express = network_tokenization_credit_card(
+ '378282246310005',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'american_express'
+ )
+
+ @google_pay_discover = network_tokenization_credit_card(
+ '6011111111111117',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'discover'
+ )
+
@billing_address = {
name: 'John Doe',
address1: '1 Market St',
@@ -141,6 +199,50 @@ def test_failure_credit
assert_equal 'INVALID_ACCOUNT', response.error_code
end
+ def test_successful_authorize_with_apple_pay
+ response = @gateway.authorize(@amount, @apple_pay, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_google_pay
+ response = @gateway.authorize(@amount, @apple_pay, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_apple_pay_jcb
+ response = @gateway.purchase(@amount, @apple_pay_jcb, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay_american_express
+ response = @gateway.purchase(@amount, @apple_pay_american_express, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_purchase_with_google_pay_master
+ response = @gateway.purchase(@amount, @google_pay_master, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_authorize_with_google_pay_discover
+ response = @gateway.purchase(@amount, @google_pay_discover, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.authorize(@amount, @visa_card, @options)
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 326f820883c..9b3f2a023ab 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -13,6 +13,34 @@ def setup
verification_value: '987',
month: 12,
year: 2031)
+ @apple_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569
+ )
+
+ @google_pay_mc = network_tokenization_credit_card(
+ '5555555555554444',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'master'
+ )
+
+ @apple_pay_jcb = network_tokenization_credit_card(
+ '3566111111111113',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'jcb'
+ )
@amount = 100
@options = {
order_id: '1',
@@ -148,6 +176,50 @@ def test_add_shipping_address
assert_equal '4158880000', address[:phoneNumber]
end
+ def test_authorize_apple_pay_visa
+ stub_comms do
+ @gateway.authorize(100, @apple_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'AceY+igABPs3jdwNaDg3MAACAAA=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '001', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_include request['consumerAuthenticationInformation'], 'cavv'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_google_pay_master_card
+ stub_comms do
+ @gateway.authorize(100, @google_pay_mc, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '002', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '012', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_equal request['consumerAuthenticationInformation']['ucafCollectionIndicator'], '2'
+ assert_include request['consumerAuthenticationInformation'], 'ucafAuthenticationData'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_apple_pay_jcb
+ stub_comms do
+ @gateway.authorize(100, @apple_pay_jcb, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '007', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '001', request['processingInformation']['paymentSolution']
+ assert_nil request['processingInformation']['commerceIndicator']
+ assert_include request['consumerAuthenticationInformation'], 'cavv'
+ end.respond_with(successful_purchase_response)
+ end
+
def test_url_building
assert_equal "#{@gateway.class.test_url}/pts/v2/action", @gateway.send(:url, 'action')
end
From 0b1043dc00a5e41ec8fc334589b052730445c1e1 Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Wed, 1 Mar 2023 14:18:02 -0500
Subject: [PATCH 029/390] CyberSourceRest: Add apple pay, google pay
Summary:
-----------------------
In order to perform ApplePay and GooglePay
transaction this commit, adds support.
Closes #4708
Remote test
-----------------------
Finished in 7.216327 seconds.
18 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit test
-----------------------
Finished in 0.032725 seconds.
15 tests, 80 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop
-----------------------
760 files inspected, no offenses detected
---
.../cyber_source/cyber_source_common.rb | 4 ++
.../billing/gateways/cyber_source_rest.rb | 38 ++++++++++++++++++-
.../gateways/remote_cyber_source_rest_test.rb | 33 +++++++++++++++-
3 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
index 4055a9197bc..9e37a41fca7 100644
--- a/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
@@ -27,6 +27,10 @@ def lookup_country_code(country_field)
country_code = Country.find(country_field)
country_code&.code(:alpha2)
end
+
+ def eligible_for_zero_auth?(payment_method, options = {})
+ payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 53a4e73ba3b..b5d67427c34 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -61,6 +61,20 @@ def credit(money, payment, options = {})
commit('credits', post)
end
+ def void(authorization, options = {})
+ payment, amount = authorization.split('|')
+ post = build_void_request(amount)
+ commit("/pts/v2/payments/#{payment}/reversals", post)
+ end
+
+ def verify(credit_card, options = {})
+ amount = eligible_for_zero_auth?(credit_card, options) ? 0 : 100
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(amount, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
def supports_scrubbing?
true
end
@@ -76,6 +90,12 @@ def scrub(transcript)
private
+ def build_void_request(amount = nil)
+ { reversalInformation: { amountDetails: { totalAmount: nil } } }.tap do |post|
+ add_reversal_amount(post, amount.to_i) if amount.present?
+ end.compact
+ end
+
def build_auth_request(amount, payment, options)
{ clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
add_customer_id(post, options)
@@ -116,6 +136,14 @@ def add_customer_id(post, options)
post[:paymentInformation][:customer] = { customerId: options[:customer_id] }
end
+ def add_reversal_amount(post, amount)
+ currency = options[:currency] || currency(amount)
+
+ post[:reversalInformation][:amountDetails] = {
+ totalAmount: localized_amount(amount, currency)
+ }
+ end
+
def add_amount(post, amount)
currency = options[:currency] || currency(amount)
@@ -232,7 +260,7 @@ def commit(action, post)
end
def success_from(response)
- %w(AUTHORIZED PENDING).include?(response['status'])
+ %w(AUTHORIZED PENDING REVERSED).include?(response['status'])
end
def message_from(response)
@@ -242,7 +270,13 @@ def message_from(response)
end
def authorization_from(response)
- response['id']
+ id = response['id']
+ has_amount = response['orderInformation'] && response['orderInformation']['amountDetails'] && response['orderInformation']['amountDetails']['authorizedAmount']
+ amount = response['orderInformation']['amountDetails']['authorizedAmount'].delete('.') if has_amount
+
+ return id if amount.blank?
+
+ [id, amount].join('|')
end
def error_code_from(response)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 11ba82a0913..8105f8ac6f7 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -121,7 +121,6 @@ def test_failure_authorize_with_declined_credit_card
def test_successful_purchase
response = @gateway.purchase(@amount, @visa_card, @options)
-
assert_success response
assert response.test?
assert_equal 'AUTHORIZED', response.message
@@ -199,6 +198,38 @@ def test_failure_credit
assert_equal 'INVALID_ACCOUNT', response.error_code
end
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.void(authorize.authorization, @options)
+ assert_success response
+ assert response.params['id'].present?
+ assert_equal 'REVERSED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_failure_void_using_card_without_funds
+ authorize = @gateway.authorize(@amount, @card_without_funds, @options)
+ response = @gateway.void(authorize.authorization, @options)
+ assert_failure response
+ assert_match %r{Declined - The request is missing one or more fields}, response.params['message']
+ assert_equal 'INVALID_REQUEST', response.params['status']
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@visa_card, @options)
+ assert_success response
+ assert response.params['id'].present?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_failure_verify
+ response = @gateway.verify(@card_without_funds, @options)
+ assert_failure response
+ assert_match %r{Decline - Invalid account number}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
def test_successful_authorize_with_apple_pay
response = @gateway.authorize(@amount, @apple_pay, @options)
From c70703d8b5f8fd3bab85b2b202507cb6fdce8563 Mon Sep 17 00:00:00 2001
From: Luis
Date: Wed, 8 Feb 2023 11:34:27 -0500
Subject: [PATCH 030/390] CybersourceREST - Void | Verify
Description
-------------------------
This integration support the following payment operations:
Verify
Void
Closes #4695
Unit test
-------------------------
Finished in 29.20384 seconds.
5468 tests, 77209 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
186.76 tests/s, 2641.23 assertions/s
Rubocop
-------------------------
Inspecting 760 files
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/cyber_source_rest.rb | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0bf4787eacc..08f34462ef3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -74,6 +74,7 @@
* BlueSnap - Add descriptor phone number field [yunnydang] #4717
* Braintree - Update transaction hash to include processor_authorization_code [yunnydang] #4718
* CyberSourceRest: Add apple pay, google pay [gasb150] #4708
+* CybersourceREST - Void | Verify [sinourain] #4695
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index b5d67427c34..36365913a0b 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -52,8 +52,9 @@ def authorize(money, payment, options = {}, capture = false)
end
def refund(money, authorization, options = {})
+ payment = authorization.split('|').first
post = build_refund_request(money, options)
- commit("payments/#{authorization}/refunds", post)
+ commit("payments/#{payment}/refunds", post)
end
def credit(money, payment, options = {})
@@ -64,7 +65,7 @@ def credit(money, payment, options = {})
def void(authorization, options = {})
payment, amount = authorization.split('|')
post = build_void_request(amount)
- commit("/pts/v2/payments/#{payment}/reversals", post)
+ commit("payments/#{payment}/reversals", post)
end
def verify(credit_card, options = {})
From cd744360440fa66a1fcbcb869bf51d83c7a0f155 Mon Sep 17 00:00:00 2001
From: cristian
Date: Fri, 3 Mar 2023 10:05:18 -0500
Subject: [PATCH 031/390] CommerceHub: adjusting reference details (#4723)
Summary:
------------------------------
Changes reference details to properly send
`referenceTransactionDetails` on capture requests
Remote Test:
------------------------------
Finished in 290.029817 seconds.
23 tests, 64 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 47.993895 seconds.
5463 tests, 77178 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
.../billing/gateways/commerce_hub.rb | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 57a1e8b2df7..8dc5c11406a 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -189,16 +189,17 @@ def build_purchase_and_auth_request(post, money, payment, options)
end
def add_reference_transaction_details(post, authorization, options, action = nil)
- post[:referenceTransactionDetails] = {}
- post[:referenceTransactionDetails][:referenceTransactionId] = authorization unless authorization.match?(/^order_id/)
+ reference_details = {}
+ merchant_reference = authorization.match?(/^order_id/) ? authorization.split('=').last : options[:reference_merchant_transaction_id]
- if action != 'capture'
- post[:referenceTransactionDetails][:referenceTransactionType] = options[:reference_transaction_type] || 'CHARGES'
+ reference_details[merchant_reference ? :referenceMerchantTransactionId : :referenceTransactionId] = merchant_reference || authorization
- order_id = authorization.split('=').last if authorization.match?(/^order_id/)
- post[:referenceTransactionDetails][:referenceMerchantTransactionId] = order_id || options[:reference_merchant_transaction_id]
- post[:referenceTransactionDetails][:referenceMerchantOrderId] = order_id || options[:reference_merchant_order_id]
+ unless action == 'capture' # capture only needs referenceTransactionId or referenceMerchantTransactionId
+ reference_details[:referenceTransactionType] = options[:reference_transaction_type] || 'CHARGES'
+ reference_details[:referenceMerchantOrderId] = merchant_reference || options[:reference_merchant_order_id]
end
+
+ post[:referenceTransactionDetails] = reference_details.compact
end
def add_invoice(post, money, options)
From 7c0e2f039160fb20ffc88761abc251777ac493c7 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Fri, 3 Mar 2023 10:06:21 -0500
Subject: [PATCH 032/390] Orbital: dismiss CardSecValInd restriction (#4724)
GWI-567
Remote:
122 tests, 509 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
144 tests, 817 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop:
760 files inspected, no offenses detected
---
lib/active_merchant/billing/gateways/orbital.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb
index c43a9f51b2e..9f1cbd9f281 100644
--- a/lib/active_merchant/billing/gateways/orbital.rb
+++ b/lib/active_merchant/billing/gateways/orbital.rb
@@ -594,7 +594,7 @@ def add_verification_value(xml, credit_card)
# Null-fill this attribute OR
# Do not submit the attribute at all.
# - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
- xml.tag! :CardSecValInd, '1' if %w(visa discover diners_club).include?(credit_card.brand) && bin == '000001'
+ xml.tag! :CardSecValInd, '1' if %w(visa discover diners_club).include?(credit_card.brand)
xml.tag! :CardSecVal, credit_card.verification_value
end
From e237ff50dfed7d08b8ac0f13bc25cdc2966402ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Santiago=20Castillo=20Garz=C3=B3n?=
Date: Wed, 1 Feb 2023 12:19:36 -0500
Subject: [PATCH 033/390] Credorax: Set default ECI values for token
transactions
Condition eci field depending on payment_method
Closes #4693
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/credorax.rb | 15 ++++++++-------
test/remote/gateways/remote_credorax_test.rb | 1 -
test/unit/gateways/credorax_test.rb | 2 +-
4 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 08f34462ef3..213826eda80 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -75,6 +75,7 @@
* Braintree - Update transaction hash to include processor_authorization_code [yunnydang] #4718
* CyberSourceRest: Add apple pay, google pay [gasb150] #4708
* CybersourceREST - Void | Verify [sinourain] #4695
+* Credorax: Set default ECI values for token transactions [sainterman] #4693
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb
index 8e4e9ee6097..6740a48e337 100644
--- a/lib/active_merchant/billing/gateways/credorax.rb
+++ b/lib/active_merchant/billing/gateways/credorax.rb
@@ -141,7 +141,7 @@ def initialize(options = {})
def purchase(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_3d_secure(post, options)
@@ -157,7 +157,7 @@ def purchase(amount, payment_method, options = {})
def authorize(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_3d_secure(post, options)
@@ -217,7 +217,7 @@ def refund(amount, authorization, options = {})
def credit(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_echo(post, options)
@@ -283,9 +283,9 @@ def add_invoice(post, money, options)
'maestro' => '9'
}
- def add_payment_method(post, payment_method)
+ def add_payment_method(post, payment_method, options)
post[:c1] = payment_method&.name || ''
- add_network_tokenization_card(post, payment_method) if payment_method.is_a? NetworkTokenizationCreditCard
+ add_network_tokenization_card(post, payment_method, options) if payment_method.is_a? NetworkTokenizationCreditCard
post[:b2] = CARD_TYPES[payment_method.brand] || ''
post[:b1] = payment_method.number
post[:b5] = payment_method.verification_value
@@ -293,9 +293,10 @@ def add_payment_method(post, payment_method)
post[:b3] = format(payment_method.month, :two_digits)
end
- def add_network_tokenization_card(post, payment_method)
+ def add_network_tokenization_card(post, payment_method, options)
post[:b21] = NETWORK_TOKENIZATION_CARD_SOURCE[payment_method.source.to_s]
- post[:token_eci] = payment_method&.eci if payment_method.source.to_s == 'network_token'
+ post[:token_eci] = post[:b21] == 'vts_mdes_token' ? '07' : nil
+ post[:token_eci] = options[:eci] || payment_method&.eci || (payment_method.brand.to_s == 'master' ? '00' : '07')
post[:token_crypto] = payment_method&.payment_cryptogram if payment_method.source.to_s == 'network_token'
end
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
index e545aa5382f..46abe4baa0e 100644
--- a/test/remote/gateways/remote_credorax_test.rb
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -66,7 +66,6 @@ def setup
@nt_credit_card = network_tokenization_credit_card('4176661000001015',
brand: 'visa',
- eci: '07',
source: :network_token,
payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA=')
end
diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb
index 2e6232c07dc..1133152a2c2 100644
--- a/test/unit/gateways/credorax_test.rb
+++ b/test/unit/gateways/credorax_test.rb
@@ -1081,7 +1081,7 @@ def test_successful_purchase_with_other_than_network_token
@gateway.purchase(@amount, @apple_pay_card)
end.check_request do |_endpoint, data, _headers|
assert_match(/b21=applepay/, data)
- assert_not_match(/token_eci=/, data)
+ assert_match(/token_eci=07/, data)
assert_not_match(/token_crypto=/, data)
end.respond_with(successful_purchase_response)
assert_success response
From 13a65c1d57beff197fcf93d1e7901d640d3063de Mon Sep 17 00:00:00 2001
From: Edgar Villamarin
Date: Wed, 1 Mar 2023 12:11:54 -0500
Subject: [PATCH 034/390] CyberSource Rest: Add ACH Support
Adding ACH/Bank Accounts to CyberSource Rest
Closes #4722
Unit test:
13 tests, 57 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test:
10 tests, 26 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
GWI-480
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 15 ++++++-
.../gateways/remote_cyber_source_rest_test.rb | 41 +++++++++++++++++++
test/unit/gateways/cyber_source_rest_test.rb | 11 ++++-
4 files changed, 66 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 213826eda80..8c3ec9978c3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -76,6 +76,7 @@
* CyberSourceRest: Add apple pay, google pay [gasb150] #4708
* CybersourceREST - Void | Verify [sinourain] #4695
* Credorax: Set default ECI values for token transactions [sainterman] #4693
+* CyberSourceRest: Add ACH Support [edgarv09] #4722
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 36365913a0b..2e316905c6d 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -30,6 +30,7 @@ class CyberSourceRestGateway < Gateway
unionpay: '062',
visa: '001'
}
+
PAYMENT_SOLUTION = {
apple_pay: '001',
google_pay: '012'
@@ -83,6 +84,7 @@ def supports_scrubbing?
def scrub(transcript)
transcript.
gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(\\?"routingNumber\\?":\\?")\d+/, '\1[FILTERED]').
gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]').
gsub(/(signature=")[^"]*/, '\1[FILTERED]').
gsub(/(keyid=")[^"]*/, '\1[FILTERED]').
@@ -154,10 +156,22 @@ def add_amount(post, amount)
}
end
+ def add_ach(post, payment)
+ post[:paymentInformation][:bank] = {
+ account: {
+ type: payment.account_type == 'checking' ? 'C' : 'S',
+ number: payment.account_number
+ },
+ routingNumber: payment.routing_number
+ }
+ end
+
def add_payment(post, payment, options)
post[:processingInformation] = {}
if payment.is_a?(NetworkTokenizationCreditCard)
add_network_tokenization_card(post, payment, options)
+ elsif payment.is_a?(Check)
+ add_ach(post, payment)
else
add_credit_card(post, payment)
end
@@ -244,7 +258,6 @@ def parse(body)
def commit(action, post)
response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post)))
-
Response.new(
success_from(response),
message_from(response),
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 8105f8ac6f7..367e2e4e5b3 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -5,6 +5,9 @@ def setup
@gateway = CyberSourceRestGateway.new(fixtures(:cybersource_rest))
@amount = 10221
@card_without_funds = credit_card('42423482938483873')
+ @bank_account = check(account_number: '4100', routing_number: '121042882')
+ @declined_bank_account = check(account_number: '550111', routing_number: '121107882')
+
@visa_card = credit_card('4111111111111111',
verification_value: '987',
month: 12,
@@ -283,4 +286,42 @@ def test_transcript_scrubbing
assert_scrubbed(@visa_card.number, transcript)
assert_scrubbed(@visa_card.verification_value, transcript)
end
+
+ def test_transcript_scrubbing_bank
+ @options[:billing_address] = @billing_address
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @bank_account, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@bank_account.account_number, transcript)
+ assert_scrubbed(@bank_account.routing_number, transcript)
+ end
+
+ def test_successful_authorize_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @bank_account, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_purchase_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.purchase(@amount, @bank_account, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_failed_authorize_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @declined_bank_account, @options)
+ assert_failure response
+ assert_equal 'Decline - General decline by the processor.', response.message
+ end
+
+ def test_failed_authorize_with_bank_account_missing_country_code
+ response = @gateway.authorize(@amount, @bank_account, @options.except(:billing_address))
+ assert_failure response
+ assert_equal 'Declined - The request is missing one or more fields', response.params['message']
+ end
end
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 9b3f2a023ab..270fcf6a262 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -9,6 +9,7 @@ def setup
public_key: 'def345',
private_key: "NYlM1sgultLjvgaraWvDCXykdz1buqOW8yXE3pMlmxQ=\n"
)
+ @bank_account = check(account_number: '4100', routing_number: '121042882')
@credit_card = credit_card('4111111111111111',
verification_value: '987',
month: 12,
@@ -56,7 +57,6 @@ def setup
},
email: 'test@cybs.com'
}
-
@gmt_time = Time.now.httpdate
@digest = 'SHA-256=gXWufV4Zc7VkN9Wkv9jh/JuAVclqDusx3vkyo3uJFWU='
@resource = '/pts/v2/payments/'
@@ -143,6 +143,15 @@ def test_add_credit_card_data
assert_equal '001', card[:type]
end
+ def test_add_ach
+ post = { paymentInformation: {} }
+ @gateway.send :add_ach, post, @bank_account
+
+ bank = post[:paymentInformation][:bank]
+ assert_equal @bank_account.account_number, bank[:account][:number]
+ assert_equal @bank_account.routing_number, bank[:routingNumber]
+ end
+
def test_add_billing_address
post = { orderInformation: {} }
From 5b3d112d00915a6b17e23aed555e7bab9d2d23af Mon Sep 17 00:00:00 2001
From: cristian
Date: Tue, 7 Mar 2023 15:12:20 -0500
Subject: [PATCH 035/390] CommerceHub: setting transactionReferenceId for
refunds (#4727)
Summary:
------------------------------
Updating the refund reference to only use referenceTransactionId
Remote Test:
------------------------------
Finished in 291.397602 seconds.
23 tests, 64 assertions, 1 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
95.6522% passed
Unit Tests:
------------------------------
Finished in 37.637689 seconds.
5474 tests, 77230 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
.../billing/gateways/commerce_hub.rb | 20 +++---
.../gateways/remote_commerce_hub_test.rb | 4 +-
test/unit/gateways/commerce_hub_test.rb | 64 +++++++++++++++++--
3 files changed, 70 insertions(+), 18 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 8dc5c11406a..4ee35d1a909 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -54,7 +54,7 @@ def capture(money, authorization, options = {})
options[:capture_flag] = true
add_invoice(post, money, options)
add_transaction_details(post, options, 'capture')
- add_reference_transaction_details(post, authorization, options, 'capture')
+ add_reference_transaction_details(post, authorization, options, :capture)
commit('sale', post, options)
end
@@ -63,7 +63,7 @@ def refund(money, authorization, options = {})
post = {}
add_invoice(post, money, options) if money
add_transaction_details(post, options)
- add_reference_transaction_details(post, authorization, options)
+ add_reference_transaction_details(post, authorization, options, :refund)
commit('refund', post, options)
end
@@ -71,7 +71,7 @@ def refund(money, authorization, options = {})
def void(authorization, options = {})
post = {}
add_transaction_details(post, options)
- add_reference_transaction_details(post, authorization, options)
+ add_reference_transaction_details(post, authorization, options, :void)
commit('void', post, options)
end
@@ -190,15 +190,15 @@ def build_purchase_and_auth_request(post, money, payment, options)
def add_reference_transaction_details(post, authorization, options, action = nil)
reference_details = {}
- merchant_reference = authorization.match?(/^order_id/) ? authorization.split('=').last : options[:reference_merchant_transaction_id]
+ merchant_reference, transaction_id = authorization.include?('|') ? authorization.split('|') : [nil, authorization]
- reference_details[merchant_reference ? :referenceMerchantTransactionId : :referenceTransactionId] = merchant_reference || authorization
-
- unless action == 'capture' # capture only needs referenceTransactionId or referenceMerchantTransactionId
- reference_details[:referenceTransactionType] = options[:reference_transaction_type] || 'CHARGES'
- reference_details[:referenceMerchantOrderId] = merchant_reference || options[:reference_merchant_order_id]
+ if action == :refund
+ reference_details[:referenceTransactionId] = transaction_id
+ else
+ reference_details[merchant_reference.present? ? :referenceMerchantTransactionId : :referenceTransactionId] = merchant_reference.presence || transaction_id
end
+ reference_details[:referenceTransactionType] = (options[:reference_transaction_type] || 'CHARGES') unless action == :capture
post[:referenceTransactionDetails] = reference_details.compact
end
@@ -349,7 +349,7 @@ def authorization_from(action, response, options)
when 'vault'
response.dig('paymentTokens', 0, 'tokenData')
when 'sale'
- options[:order_id].present? ? "order_id=#{options[:order_id]}" : response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
+ [options[:order_id] || '', response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')].join('|')
else
response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
end
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index 66e1c1985a9..c7ceabfc03f 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -128,7 +128,7 @@ def test_successful_authorize_and_void_using_store_id_as_reference
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
- assert_equal "order_id=#{@options[:order_id]}", response.authorization
+ assert_match(/#{@options[:order_id]}|\w*/, response.authorization)
response = @gateway.void(response.authorization, @options)
assert_success response
@@ -189,7 +189,7 @@ def test_successful_purchase_and_partial_refund
end
def test_failed_refund
- response = @gateway.refund(nil, '123', @options)
+ response = @gateway.refund(nil, 'abc123|123', @options)
assert_failure response
assert_equal 'Referenced transaction is invalid or not found', response.message
end
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index e4fefa90a70..857262b4a30 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -33,6 +33,7 @@ def setup
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
@options = {}
+ @post = {}
end
def test_successful_purchase
@@ -153,11 +154,11 @@ def test_successful_parsing_of_billing_and_shipping_addresses
def test_successful_void
response = stub_comms do
- @gateway.void('authorization123', @options)
+ @gateway.void('abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
- assert_equal request['referenceTransactionDetails']['referenceTransactionType'], 'CHARGES'
+ assert_equal 'abc123', request['referenceTransactionDetails']['referenceMerchantTransactionId']
+ assert_equal 'CHARGES', request['referenceTransactionDetails']['referenceTransactionType']
assert_nil request['transactionDetails']['captureFlag']
end.respond_with(successful_void_and_refund_response)
@@ -166,7 +167,7 @@ def test_successful_void
def test_successful_refund
response = stub_comms do
- @gateway.refund(nil, 'authorization123', @options)
+ @gateway.refund(nil, 'abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
@@ -180,7 +181,7 @@ def test_successful_refund
def test_successful_partial_refund
response = stub_comms do
- @gateway.refund(@amount - 1, 'authorization123', @options)
+ @gateway.refund(@amount - 1, 'abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
@@ -256,7 +257,7 @@ def test_uses_order_id_to_keep_transaction_references_when_provided
end.respond_with(successful_purchase_response)
assert_success response
- assert_equal 'order_id=abc123', response.authorization
+ assert_equal 'abc123|6304d53be8d94312a620962afc9c012d', response.authorization
end
def test_detect_success_state_for_verify_on_success_transaction
@@ -279,6 +280,57 @@ def test_detect_success_state_for_verify_on_failure_transaction
refute @gateway.send :success_from, gateway_resp, 'verify'
end
+ def test_add_reference_transaction_details_capture_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :capture
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_nil @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
+ assert_nil @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_add_reference_transaction_details_capture_order_id
+ authorization = 'abc123|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :capture
+ assert_equal 'abc123', @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
+ assert_nil @post[:referenceTransactionDetails][:referenceTransactionType]
+ assert_nil @post[:referenceTransactionDetails][:referenceTransactionId]
+ end
+
+ def test_add_reference_transaction_details_void_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :void
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_add_reference_transaction_details_void_order_id
+ authorization = 'abc123|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :void
+ assert_equal 'abc123', @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ assert_nil @post[:referenceTransactionDetails][:referenceTransactionId]
+ end
+
+ def test_add_reference_transaction_details_refund_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :refund
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_add_reference_transaction_details_refund_order_id
+ authorization = 'abc123|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :refund
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
private
def successful_purchase_response
From b7177efbc3303f1d527625e8fe2ccede4715ff48 Mon Sep 17 00:00:00 2001
From: cristian
Date: Mon, 6 Mar 2023 11:43:48 -0500
Subject: [PATCH 036/390] Cybersource REST: Adding capture request
Summary:
------------------------------
Adding the capture functionality to the Cybersource REST
gateway
Closes #4726
Remote Test:
------------------------------
Finished in 25.504733 seconds.
25 tests, 89 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 39.743032 seconds.
5468 tests, 77209 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 13 +++++++---
.../gateways/remote_cyber_source_rest_test.rb | 24 +++++++++++++++++++
3 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8c3ec9978c3..512decd3956 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -77,6 +77,7 @@
* CybersourceREST - Void | Verify [sinourain] #4695
* Credorax: Set default ECI values for token transactions [sainterman] #4693
* CyberSourceRest: Add ACH Support [edgarv09] #4722
+* CybersourceREST: Add capture request [heavyblade] #4726
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 2e316905c6d..7291035aa78 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -52,9 +52,16 @@ def authorize(money, payment, options = {}, capture = false)
commit('payments', post)
end
+ def capture(money, authorization, options = {})
+ payment = authorization.split('|').first
+ post = build_reference_request(money, options)
+
+ commit("payments/#{payment}/captures", post)
+ end
+
def refund(money, authorization, options = {})
payment = authorization.split('|').first
- post = build_refund_request(money, options)
+ post = build_reference_request(money, options)
commit("payments/#{payment}/refunds", post)
end
@@ -110,7 +117,7 @@ def build_auth_request(amount, payment, options)
end.compact
end
- def build_refund_request(amount, options)
+ def build_reference_request(amount, options)
{ clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
add_code(post, options)
add_amount(post, amount)
@@ -280,7 +287,7 @@ def success_from(response)
def message_from(response)
return response['status'] if success_from(response)
- response['errorInformation']['message']
+ response['errorInformation']['message'] || response['message']
end
def authorization_from(response)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 367e2e4e5b3..ccd06431234 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -122,6 +122,30 @@ def test_failure_authorize_with_declined_credit_card
assert_equal 'INVALID_ACCOUNT', response.error_code
end
+ def test_successful_capture
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.capture(@amount, authorize.authorization, @options)
+
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_capture_with_partial_amount
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.capture(@amount - 10, authorize.authorization, @options)
+
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_failure_capture_with_higher_amount
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.capture(@amount + 10, authorize.authorization, @options)
+
+ assert_failure response
+ assert_match(/exceeds/, response.params['message'])
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @visa_card, @options)
assert_success response
From 9a01cf2c6ec9be8d3a554315f10806fe678af10e Mon Sep 17 00:00:00 2001
From: aenand
Date: Tue, 7 Mar 2023 11:03:15 -0500
Subject: [PATCH 037/390] Paymentez: Add inquire by transaction_id
Get payment status by Paymentez transaction id
Unit:
30 tests, 127 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
34 tests, 73 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
73.5294% passed
** These failures also existed on the main branch **
---
CHANGELOG | 1 +
.../billing/gateways/paymentez.rb | 24 ++++++++++++++-----
test/remote/gateways/remote_paymentez_test.rb | 9 +++++++
test/unit/gateways/paymentez_test.rb | 12 ++++++++++
4 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 512decd3956..248cf30e318 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -78,6 +78,7 @@
* Credorax: Set default ECI values for token transactions [sainterman] #4693
* CyberSourceRest: Add ACH Support [edgarv09] #4722
* CybersourceREST: Add capture request [heavyblade] #4726
+* Paymentez: Add transaction inquire request [aenand] #4729
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/paymentez.rb b/lib/active_merchant/billing/gateways/paymentez.rb
index 3e71b1e1989..cc033bcddb3 100644
--- a/lib/active_merchant/billing/gateways/paymentez.rb
+++ b/lib/active_merchant/billing/gateways/paymentez.rb
@@ -137,6 +137,10 @@ def unstore(identification, options = {})
commit_card('delete', post)
end
+ def inquire(authorization, options = {})
+ commit_transaction('inquire', authorization)
+ end
+
def supports_scrubbing?
true
end
@@ -232,12 +236,20 @@ def parse(body)
end
def commit_raw(object, action, parameters)
- url = "#{(test? ? test_url : live_url)}#{object}/#{action}"
-
- begin
- raw_response = ssl_post(url, post_data(parameters), headers)
- rescue ResponseError => e
- raw_response = e.response.body
+ if action == 'inquire'
+ url = "#{(test? ? test_url : live_url)}#{object}/#{parameters}"
+ begin
+ raw_response = ssl_get(url, headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+ else
+ url = "#{(test? ? test_url : live_url)}#{object}/#{action}"
+ begin
+ raw_response = ssl_post(url, post_data(parameters), headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
end
begin
diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb
index 37d3fced73c..82d7db2bbb9 100644
--- a/test/remote/gateways/remote_paymentez_test.rb
+++ b/test/remote/gateways/remote_paymentez_test.rb
@@ -303,6 +303,15 @@ def test_unstore_with_elo
assert_success response
end
+ def test_successful_inquire_with_transaction_id
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ gateway_transaction_id = response.authorization
+ response = @gateway.inquire(gateway_transaction_id, @options)
+ assert_success response
+ end
+
def test_invalid_login
gateway = PaymentezGateway.new(application_code: '9z8y7w6x', app_key: '1a2b3c4d')
diff --git a/test/unit/gateways/paymentez_test.rb b/test/unit/gateways/paymentez_test.rb
index b8b1924d096..a3019c5f9c0 100644
--- a/test/unit/gateways/paymentez_test.rb
+++ b/test/unit/gateways/paymentez_test.rb
@@ -341,6 +341,18 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_successful_inquire_with_transaction_id
+ response = stub_comms(@gateway, :ssl_get) do
+ @gateway.inquire('CI-635')
+ end.check_request do |method, _endpoint, _data, _headers|
+ assert_match('https://ccapi-stg.paymentez.com/v2/transaction/CI-635', method)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal 'CI-635', response.authorization
+ assert response.test?
+ end
+
private
def pre_scrubbed
From a40aaa47af606f59791c4c80de7fe10763a3d203 Mon Sep 17 00:00:00 2001
From: Luis
Date: Thu, 9 Mar 2023 10:41:23 -0500
Subject: [PATCH 038/390] Cybersource Rest - update message response on error
Description
-------------------------
Update message response on error in order to get a more redeable response
GWI-571
Unit test
-------------------------
Finished in 30.871357 seconds.
5476 tests, 77239 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
177.38 tests/s, 2501.96 assertions/s
Rubocop
-------------------------
760 files inspected, no offenses detected
---
lib/active_merchant/billing/gateways/cyber_source_rest.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 7291035aa78..69f4fbd7663 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -277,7 +277,8 @@ def commit(action, post)
)
rescue ActiveMerchant::ResponseError => e
response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } }
- Response.new(false, response.dig('response', 'rmsg'), response, test: test?)
+ message = response.dig('response', 'rmsg') || response.dig('message')
+ Response.new(false, message, response, test: test?)
end
def success_from(response)
From 0ac72dd9d7f8389ab010033f9808a45de911de45 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 2 Mar 2023 16:28:30 -0600
Subject: [PATCH 039/390] Ebanx: Add transaction inquire request
Get transaction by authorization.
Remote:
27 tests, 73 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96.2963% passed
Unit:
20 tests, 89 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ebanx.rb | 26 ++++++++++++++-----
test/remote/gateways/remote_ebanx_test.rb | 10 +++++++
test/unit/gateways/ebanx_test.rb | 12 +++++++++
4 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 248cf30e318..9d96d5d4c1b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -79,6 +79,7 @@
* CyberSourceRest: Add ACH Support [edgarv09] #4722
* CybersourceREST: Add capture request [heavyblade] #4726
* Paymentez: Add transaction inquire request [aenand] #4729
+* Ebanx: Add transaction inquire request [almalee24] #4725
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index 11e5bb68945..085e458448b 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -27,7 +27,8 @@ class EbanxGateway < Gateway
capture: 'capture',
refund: 'refund',
void: 'cancel',
- store: 'token'
+ store: 'token',
+ inquire: 'query'
}
HTTP_METHOD = {
@@ -36,7 +37,8 @@ class EbanxGateway < Gateway
capture: :get,
refund: :post,
void: :get,
- store: :post
+ store: :post,
+ inquire: :get
}
VERIFY_AMOUNT_PER_COUNTRY = {
@@ -126,6 +128,14 @@ def verify(credit_card, options = {})
end
end
+ def inquire(authorization, options = {})
+ post = {}
+ add_integration_key(post)
+ add_authorization(post, authorization)
+
+ commit(:inquire, post)
+ end
+
def supports_scrubbing?
true
end
@@ -257,13 +267,15 @@ def add_processing_type_to_commit_headers(commit_headers, processing_type)
end
def success_from(action, response)
+ payment_status = response.try(:[], 'payment').try(:[], 'status')
+
if %i[purchase capture refund].include?(action)
- response.try(:[], 'payment').try(:[], 'status') == 'CO'
+ payment_status == 'CO'
elsif action == :authorize
- response.try(:[], 'payment').try(:[], 'status') == 'PE'
+ payment_status == 'PE'
elsif action == :void
- response.try(:[], 'payment').try(:[], 'status') == 'CA'
- elsif action == :store
+ payment_status == 'CA'
+ elsif %i[store inquire].include?(action)
response.try(:[], 'status') == 'SUCCESS'
else
false
@@ -298,7 +310,7 @@ def url_for(hostname, action, parameters)
end
def requires_http_get(action)
- return true if %i[capture void].include?(action)
+ return true if %i[capture void inquire].include?(action)
false
end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
index 3c9624cb0a7..f49dc7535fe 100644
--- a/test/remote/gateways/remote_ebanx_test.rb
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -261,6 +261,16 @@ def test_failed_verify
assert_match %r{Invalid card or card type}, response.message
end
+ def test_successful_inquire
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ inquire = @gateway.inquire(purchase.authorization)
+ assert_success inquire
+
+ assert_equal 'Accepted', purchase.message
+ end
+
def test_invalid_login
gateway = EbanxGateway.new(integration_key: '')
diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb
index 06e3d4b6db0..277693e4b4a 100644
--- a/test/unit/gateways/ebanx_test.rb
+++ b/test/unit/gateways/ebanx_test.rb
@@ -172,6 +172,18 @@ def test_successful_store_and_purchase
assert_success response
end
+ def test_successful_purchase_and_inquire
+ @gateway.expects(:ssl_request).returns(successful_purchase_response)
+
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ @gateway.expects(:ssl_request).returns(successful_purchase_response)
+ response = @gateway.inquire(purchase.authorization)
+
+ assert_success response
+ end
+
def test_error_response_with_invalid_creds
@gateway.expects(:ssl_request).returns(invalid_cred_response)
From fcafb3c274591f6f05d5517290f0347555496c1d Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Mon, 13 Mar 2023 10:34:27 -0500
Subject: [PATCH 040/390] CommerceHub: Update fields for transactions with
sotred credentials (#4733)
Description
-------------------------
This commit add new fields for transactions with stored credentials options
and remove the current referenceMerchantTransactionId in order to use
referenceTransactionId
[SER-504](https://spreedly.atlassian.net/browse/SER-504)
[SER-536](https://spreedly.atlassian.net/browse/SER-536)
Unit test
-------------------------
Finished in 0.01392 seconds.
22 tests, 147 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
1580.46 tests/s, 10560.34 assertions/s
Remote test
-------------------------
Finished in 296.371956 seconds.
24 tests, 63 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
0.08 tests/s, 0.21 assertions/s
Rubocop
-------------------------
760 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
.../billing/gateways/commerce_hub.rb | 14 ++--
.../gateways/remote_commerce_hub_test.rb | 37 ++++++----
test/unit/gateways/commerce_hub_test.rb | 72 +++++++++++--------
3 files changed, 73 insertions(+), 50 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 4ee35d1a909..b552e3b4697 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -111,9 +111,12 @@ def scrub(transcript)
def add_transaction_interaction(post, options)
post[:transactionInteraction] = {}
- post[:transactionInteraction][:origin] = options[:transaction_origin] || 'ECOM'
+ post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
post[:transactionInteraction][:eciIndicator] = options[:eci_indicator] || 'CHANNEL_ENCRYPTED'
post[:transactionInteraction][:posConditionCode] = options[:pos_condition_code] || 'CARD_NOT_PRESENT_ECOM'
+ post[:transactionInteraction][:posEntryMode] = options[:pos_entry_mode] || 'MANUAL'
+ post[:transactionInteraction][:additionalPosInformation] = {}
+ post[:transactionInteraction][:additionalPosInformation][:dataEntrySource] = options[:data_entry_source] || 'UNSPECIFIED'
end
def add_transaction_details(post, options, action = nil)
@@ -190,14 +193,9 @@ def build_purchase_and_auth_request(post, money, payment, options)
def add_reference_transaction_details(post, authorization, options, action = nil)
reference_details = {}
- merchant_reference, transaction_id = authorization.include?('|') ? authorization.split('|') : [nil, authorization]
-
- if action == :refund
- reference_details[:referenceTransactionId] = transaction_id
- else
- reference_details[merchant_reference.present? ? :referenceMerchantTransactionId : :referenceTransactionId] = merchant_reference.presence || transaction_id
- end
+ _merchant_reference, transaction_id = authorization.include?('|') ? authorization.split('|') : [nil, authorization]
+ reference_details[:referenceTransactionId] = transaction_id
reference_details[:referenceTransactionType] = (options[:reference_transaction_type] || 'CHARGES') unless action == :capture
post[:referenceTransactionDetails] = reference_details.compact
end
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index c7ceabfc03f..caf2667146f 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -4,7 +4,7 @@ class RemoteCommerceHubTest < Test::Unit::TestCase
def setup
# Uncomment the sleep if you want to run the entire set of remote tests without
# getting 'The transaction limit was exceeded. Please try again!' errors
- # sleep 5
+ # sleep 10
@gateway = CommerceHubGateway.new(fixtures(:commerce_hub))
@@ -32,6 +32,7 @@ def setup
source: :apple_pay,
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
+ @master_card = credit_card('5454545454545454', brand: 'master')
@options = {}
end
@@ -41,6 +42,27 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_purchase_with_gsf_mit
+ @options[:data_entry_source] = 'ELECTRONIC_PAYMENT_TERMINAL'
+ @options[:pos_entry_mode] = 'CONTACTLESS'
+ response = @gateway.purchase(@amount, @master_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_cit_with_gsf
+ stored_credential_options = {
+ initial_transaction: true,
+ reason_type: 'cardholder',
+ initiator: 'unscheduled'
+ }
+ @options[:eci_indicator] = 'CHANNEL_ENCRYPTED'
+ @options[:stored_credential] = stored_credential_options
+ response = @gateway.purchase(@amount, @master_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
def test_successful_purchase_with_failed_avs_cvv_response_codes
@options[:billing_address] = {
address1: '112 Main St.',
@@ -122,19 +144,6 @@ def test_successful_authorize_and_void
assert_equal 'Approved', response.message
end
- def test_successful_authorize_and_void_using_store_id_as_reference
- @options[:order_id] = SecureRandom.hex(16)
-
- response = @gateway.authorize(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'Approved', response.message
- assert_match(/#{@options[:order_id]}|\w*/, response.authorization)
-
- response = @gateway.void(response.authorization, @options)
- assert_success response
- assert_equal 'Approved', response.message
- end
-
def test_failed_void
response = @gateway.void('123', @options)
assert_failure response
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index 857262b4a30..23491dcdfcc 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -157,7 +157,7 @@ def test_successful_void
@gateway.void('abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal 'abc123', request['referenceTransactionDetails']['referenceMerchantTransactionId']
+ assert_equal 'authorization123', request['referenceTransactionDetails']['referenceTransactionId']
assert_equal 'CHARGES', request['referenceTransactionDetails']['referenceTransactionType']
assert_nil request['transactionDetails']['captureFlag']
end.respond_with(successful_void_and_refund_response)
@@ -194,6 +194,49 @@ def test_successful_partial_refund
assert_success response
end
+ def test_successful_purchase_cit_with_gsf
+ options = stored_credential_options(:cardholder, :unscheduled, :initial)
+ options[:data_entry_source] = 'MOBILE_WEB'
+ options[:pos_entry_mode] = 'MANUAL'
+ options[:pos_condition_code] = 'CARD_PRESENT'
+ response = stub_comms do
+ @gateway.purchase(@amount, 'authorization123', options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['transactionInteraction']['origin'], 'ECOM'
+ assert_equal request['transactionInteraction']['eciIndicator'], 'CHANNEL_ENCRYPTED'
+ assert_equal request['transactionInteraction']['posConditionCode'], 'CARD_PRESENT'
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
+ assert_equal request['transactionInteraction']['additionalPosInformation']['dataEntrySource'], 'MOBILE_WEB'
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def test_successful_purchase_mit_with_gsf
+ options = stored_credential_options(:merchant, :recurring)
+ options[:origin] = 'POS'
+ options[:pos_entry_mode] = 'MANUAL'
+ options[:data_entry_source] = 'MOBILE_WEB'
+ response = stub_comms do
+ @gateway.purchase(@amount, 'authorization123', options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['transactionInteraction']['origin'], 'POS'
+ assert_equal request['transactionInteraction']['eciIndicator'], 'CHANNEL_ENCRYPTED'
+ assert_equal request['transactionInteraction']['posConditionCode'], 'CARD_NOT_PRESENT_ECOM'
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
+ assert_equal request['transactionInteraction']['additionalPosInformation']['dataEntrySource'], 'MOBILE_WEB'
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def stored_credential_options(*args, ntid: nil)
+ {
+ order_id: '#1001',
+ stored_credential: stored_credential(*args, ntid: ntid)
+ }
+ end
+
def test_successful_store
response = stub_comms do
@gateway.store(@credit_card, @options)
@@ -285,19 +328,9 @@ def test_add_reference_transaction_details_capture_reference_id
@gateway.send :add_reference_transaction_details, @post, authorization, {}, :capture
assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
- assert_nil @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
assert_nil @post[:referenceTransactionDetails][:referenceTransactionType]
end
- def test_add_reference_transaction_details_capture_order_id
- authorization = 'abc123|922e-59fc86a36c03'
-
- @gateway.send :add_reference_transaction_details, @post, authorization, {}, :capture
- assert_equal 'abc123', @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
- assert_nil @post[:referenceTransactionDetails][:referenceTransactionType]
- assert_nil @post[:referenceTransactionDetails][:referenceTransactionId]
- end
-
def test_add_reference_transaction_details_void_reference_id
authorization = '|922e-59fc86a36c03'
@@ -306,15 +339,6 @@ def test_add_reference_transaction_details_void_reference_id
assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
end
- def test_add_reference_transaction_details_void_order_id
- authorization = 'abc123|922e-59fc86a36c03'
-
- @gateway.send :add_reference_transaction_details, @post, authorization, {}, :void
- assert_equal 'abc123', @post[:referenceTransactionDetails][:referenceMerchantTransactionId]
- assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
- assert_nil @post[:referenceTransactionDetails][:referenceTransactionId]
- end
-
def test_add_reference_transaction_details_refund_reference_id
authorization = '|922e-59fc86a36c03'
@@ -323,14 +347,6 @@ def test_add_reference_transaction_details_refund_reference_id
assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
end
- def test_add_reference_transaction_details_refund_order_id
- authorization = 'abc123|922e-59fc86a36c03'
-
- @gateway.send :add_reference_transaction_details, @post, authorization, {}, :refund
- assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
- assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
- end
-
private
def successful_purchase_response
From ef66950cfd0728469da303f6a35c1c6e89660476 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 17 Feb 2023 10:11:16 -0600
Subject: [PATCH 041/390] Ebanx: Add support of Elo & Hipercard
For all credit card transactions Ebanx only requires payment_type_code to
be 'creditcard' no matter the card. This removes the need of specifiying
the card brand in Ebanx transaction.
Unit:
19 tests, 84 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
28 tests, 74 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96.4286% passed
---
CHANGELOG | 1 +
.../billing/credit_card_methods.rb | 18 ++++++---
lib/active_merchant/billing/gateways/ebanx.rb | 22 +++++------
test/remote/gateways/remote_ebanx_test.rb | 38 +++++++++++++++++++
4 files changed, 60 insertions(+), 19 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9d96d5d4c1b..8b9feb4c170 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -80,6 +80,7 @@
* CybersourceREST: Add capture request [heavyblade] #4726
* Paymentez: Add transaction inquire request [aenand] #4729
* Ebanx: Add transaction inquire request [almalee24] #4725
+* Ebanx: Add support for Elo & Hipercard [almalee24] #4702
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 154f06b2556..33611bcab3a 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -45,7 +45,8 @@ module CreditCardMethods
'passcard' => ->(num) { num =~ /^628026\d{10}$/ },
'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
'anda' => ->(num) { num =~ /^603199\d{10}$/ },
- 'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ }
+ 'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
+ 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) }
}
SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -193,10 +194,10 @@ module CreditCardMethods
506745..506747, 506753..506753, 506774..506778, 509000..509007, 509009..509014,
509020..509030, 509035..509042, 509044..509089, 509091..509101, 509104..509807,
509831..509877, 509897..509900, 509918..509964, 509971..509986, 509995..509999,
- 627780..627780, 636368..636368, 650031..650033, 650035..650051, 650057..650081,
- 650406..650439, 650485..650504, 650506..650538, 650552..650598, 650720..650727,
- 650901..650922, 650928..650928, 650938..650939, 650946..650978, 651652..651704,
- 655000..655019, 655021..655057
+ 627780..627780, 636297..636298, 636368..636368, 650031..650033, 650035..650051,
+ 650057..650081, 650406..650439, 650485..650504, 650506..650538, 650552..650598,
+ 650720..650727, 650901..650922, 650928..650928, 650938..650939, 650946..650978,
+ 651652..651704, 655000..655019, 655021..655057
]
# Alelo provides BIN ranges by e-mailing them out periodically.
@@ -241,6 +242,11 @@ module CreditCardMethods
3528..3589, 3088..3094, 3096..3102, 3112..3120, 3158..3159, 3337..3349
]
+ HIPERCARD_RANGES = [
+ 384100..384100, 384140..384140, 384160..384160, 606282..606282, 637095..637095,
+ 637568..637568, 637599..637599, 637609..637609, 637612..637612
+ ]
+
def self.included(base)
base.extend(ClassMethods)
end
@@ -404,7 +410,7 @@ def valid_by_algorithm?(brand, numbers) #:nodoc:
valid_naranja_algo?(numbers)
when 'creditel'
valid_creditel_algo?(numbers)
- when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d'
+ when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d', 'hipercard'
true
when 'sodexo'
sodexo_no_luhn?(numbers) ? true : valid_luhn?(numbers)
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index 085e458448b..b907855d50a 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -6,21 +6,13 @@ class EbanxGateway < Gateway
self.supported_countries = %w(BR MX CO CL AR PE)
self.default_currency = 'USD'
- self.supported_cardtypes = %i[visa master american_express discover diners_club]
+ self.supported_cardtypes = %i[visa master american_express discover diners_club elo hipercard]
self.homepage_url = 'http://www.ebanx.com/'
self.display_name = 'EBANX'
TAGS = ['Spreedly']
- CARD_BRAND = {
- visa: 'visa',
- master: 'master_card',
- american_express: 'amex',
- discover: 'discover',
- diners_club: 'diners'
- }
-
URL_MAP = {
purchase: 'direct',
authorize: 'direct',
@@ -199,14 +191,14 @@ def add_invoice(post, money, options)
end
def add_card_or_token(post, payment, options)
- payment, brand = payment.split('|') if payment.is_a?(String)
- post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym]
+ payment = payment.split('|')[0] if payment.is_a?(String)
+ post[:payment][:payment_type_code] = 'creditcard'
post[:payment][:creditcard] = payment_details(payment)
post[:payment][:creditcard][:soft_descriptor] = options[:soft_descriptor] if options[:soft_descriptor]
end
def add_payment_details(post, payment)
- post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
+ post[:payment_type_code] = 'creditcard'
post[:creditcard] = payment_details(payment)
end
@@ -290,7 +282,11 @@ def message_from(response)
def authorization_from(action, parameters, response)
if action == :store
- "#{response.try(:[], 'token')}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}"
+ if success_from(action, response)
+ "#{response.try(:[], 'token')}|#{response['payment_type_code']}"
+ else
+ response.try(:[], 'token')
+ end
else
response.try(:[], 'payment').try(:[], 'hash')
end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
index f49dc7535fe..324a7d87f86 100644
--- a/test/remote/gateways/remote_ebanx_test.rb
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -26,6 +26,9 @@ def setup
tags: EbanxGateway::TAGS,
soft_descriptor: 'ActiveMerchant'
}
+
+ @hiper_card = credit_card('6062825624254001')
+ @elo_card = credit_card('6362970000457013')
end
def test_successful_purchase
@@ -34,6 +37,24 @@ def test_successful_purchase
assert_equal 'Accepted', response.message
end
+ def test_successful_purchase_hipercard
+ response = @gateway.purchase(@amount, @hiper_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_purchase_elocard
+ response = @gateway.purchase(@amount, @elo_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_store_elocard
+ response = @gateway.purchase(@amount, @elo_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
def test_successful_purchase_with_more_options
options = @options.merge({
order_id: generate_unique_id,
@@ -194,6 +215,23 @@ def test_successful_store_and_purchase_as_brazil_business
store = @gateway.store(@credit_card, options)
assert_success store
+ assert_equal store.authorization.split('|')[1], 'visa'
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, options)
+ assert_success purchase
+ assert_equal 'Accepted', purchase.message
+ end
+
+ def test_successful_store_and_purchase_as_brazil_business_with_hipercard
+ options = @options.update(document: '32593371000110',
+ person_type: 'business',
+ responsible_name: 'Business Person',
+ responsible_document: '32593371000111',
+ responsible_birth_date: '1/11/1975')
+
+ store = @gateway.store(@hiper_card, options)
+ assert_success store
+ assert_equal store.authorization.split('|')[1], 'hipercard'
assert purchase = @gateway.purchase(@amount, store.authorization, options)
assert_success purchase
From 01c0d15179dc6b350bcb86cbdae86903098a4251 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 6 Mar 2023 21:25:22 -0800
Subject: [PATCH 042/390] Checkout_v2: Add idempotency key support
This PR is to add the support for an optional idempotency key through the header during requests and should be available to all actions 'purchase, authorize, and etc'. I did note that the failing remote tests were sending back 401 unauthorize even when on the latest upstream master. Test results below:
Local:
5469 tests, 77162 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications
99.6526% passed
Unit:
54 tests, 300 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
89 tests, 213 assertions, 4 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
95.5056% passed
---
CHANGELOG | 1 +
.../billing/gateways/checkout_v2.rb | 30 ++++++++++---------
.../gateways/remote_checkout_v2_test.rb | 6 ++++
test/unit/gateways/checkout_v2_test.rb | 11 +++++++
4 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8b9feb4c170..e274b3b9987 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -81,6 +81,7 @@
* Paymentez: Add transaction inquire request [aenand] #4729
* Ebanx: Add transaction inquire request [almalee24] #4725
* Ebanx: Add support for Elo & Hipercard [almalee24] #4702
+* CheckoutV2: Add Idempotency key support [yunnydang] #4728
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 5fd41ea62e7..34fcbcf2165 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -34,14 +34,14 @@ def purchase(amount, payment_method, options = {})
post = {}
build_auth_or_purchase(post, amount, payment_method, options)
- commit(:purchase, post)
+ commit(:purchase, post, options)
end
def authorize(amount, payment_method, options = {})
post = {}
post[:capture] = false
build_auth_or_purchase(post, amount, payment_method, options)
- options[:incremental_authorization] ? commit(:incremental_authorize, post, options[:incremental_authorization]) : commit(:authorize, post)
+ options[:incremental_authorization] ? commit(:incremental_authorize, post, options, options[:incremental_authorization]) : commit(:authorize, post, options)
end
def capture(amount, authorization, options = {})
@@ -51,7 +51,7 @@ def capture(amount, authorization, options = {})
add_customer_data(post, options)
add_metadata(post, options)
- commit(:capture, post, authorization)
+ commit(:capture, post, options, authorization)
end
def credit(amount, payment, options = {})
@@ -63,14 +63,14 @@ def credit(amount, payment, options = {})
add_payment_method(post, payment, options, :destination)
add_source(post, options)
- commit(:credit, post)
+ commit(:credit, post, options)
end
def void(authorization, _options = {})
post = {}
add_metadata(post, options)
- commit(:void, post, authorization)
+ commit(:void, post, options, authorization)
end
def refund(amount, authorization, options = {})
@@ -79,7 +79,7 @@ def refund(amount, authorization, options = {})
add_customer_data(post, options)
add_metadata(post, options)
- commit(:refund, post, authorization)
+ commit(:refund, post, options, authorization)
end
def verify(credit_card, options = {})
@@ -87,7 +87,7 @@ def verify(credit_card, options = {})
end
def verify_payment(authorization, option = {})
- commit(:verify_payment, nil, authorization, :get)
+ commit(:verify_payment, nil, options, authorization, :get)
end
def supports_scrubbing?
@@ -121,13 +121,13 @@ def store(payment_method, options = {})
add_payment_method(post, token, options)
post.merge!(post.delete(:source))
add_customer_data(post, options)
- r.process { commit(:store, post) }
+ r.process { commit(:store, post, options) }
end
end
end
def unstore(id, options = {})
- commit(:unstore, nil, id, :delete)
+ commit(:unstore, nil, options, id, :delete)
end
private
@@ -319,9 +319,9 @@ def setup_access_token
response['access_token']
end
- def commit(action, post, authorization = nil, method = :post)
+ def commit(action, post, options, authorization = nil, method = :post)
begin
- raw_response = ssl_request(method, url(action, authorization), post.nil? || post.empty? ? nil : post.to_json, headers(action))
+ raw_response = ssl_request(method, url(action, authorization), post.nil? || post.empty? ? nil : post.to_json, headers(action, options))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
source_id = authorization if action == :unstore
@@ -354,13 +354,15 @@ def response(action, succeeded, response, source_id = nil)
)
end
- def headers(action)
+ def headers(action, options)
auth_token = @access_token ? "Bearer #{@access_token}" : @options[:secret_key]
auth_token = @options[:public_key] if action == :tokens
- {
+ headers = {
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
+ headers['Cko-Idempotency-Key'] = options[:cko_idempotency_key] if options[:cko_idempotency_key]
+ headers
end
def tokenize(payment_method, options = {})
@@ -368,7 +370,7 @@ def tokenize(payment_method, options = {})
add_authorization_type(post, options)
add_payment_method(post, payment_method, options)
add_customer_data(post, options)
- commit(:tokens, post[:source])
+ commit(:tokens, post[:source], options)
end
def url(action, authorization)
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 2275c51bfc1..1e9ad0fbfdf 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -819,4 +819,10 @@ def test_expired_card_returns_error_code
assert_equal 'request_invalid: card_expired', response.message
assert_equal 'request_invalid: card_expired', response.error_code
end
+
+ def test_successful_purchase_with_idempotency_key
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(cko_idempotency_key: 'test123'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
end
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 18c0734432a..882f5291d84 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -434,6 +434,17 @@ def test_successful_purchase_with_metadata
assert_success response
end
+ def test_optional_idempotency_key_header
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ cko_idempotency_key: 'test123'
+ }
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _url, _data, headers|
+ assert_equal 'test123', headers['Cko-Idempotency-Key']
+ end.respond_with(successful_authorize_response)
+ end
+
def test_successful_authorize_and_capture_with_metadata
response = stub_comms(@gateway, :ssl_request) do
options = {
From e8a247c0f87bf141947fede0ef99eaa04f3b2798 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 13 Mar 2023 16:03:31 -0700
Subject: [PATCH 043/390] Adyen: add support for shopper_statement field for
capture action
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 9 +++++++++
test/remote/gateways/remote_adyen_test.rb | 8 ++++++++
test/unit/gateways/adyen_test.rb | 8 ++++++++
4 files changed, 26 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index e274b3b9987..1200d69303e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -82,6 +82,7 @@
* Ebanx: Add transaction inquire request [almalee24] #4725
* Ebanx: Add support for Elo & Hipercard [almalee24] #4702
* CheckoutV2: Add Idempotency key support [yunnydang] #4728
+* Adyen: Add support for shopper_statement field for capture [yunnydang] #PR 4736
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index f89f0e6417c..5b311b9c6fb 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -77,6 +77,7 @@ def capture(money, authorization, options = {})
add_reference(post, authorization, options)
add_splits(post, options)
add_network_transaction_reference(post, options)
+ add_shopper_statement(post, options)
commit('capture', post, options)
end
@@ -299,6 +300,14 @@ def add_shopper_data(post, options)
post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
end
+ def add_shopper_statement(post, options)
+ return unless options[:shopper_statement]
+
+ post[:additionalData] = {
+ shopperStatement: options[:shopper_statement]
+ }
+ end
+
def add_merchant_data(post, options)
post[:additionalData][:subMerchantID] = options[:sub_merchant_id] if options[:sub_merchant_id]
post[:additionalData][:subMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index d4fb596ba21..83b43d71b05 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -1343,6 +1343,14 @@ def test_auth_capture_refund_with_network_txn_id
assert_success refund
end
+ def test_successful_capture_with_shopper_statement
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(shopper_statement: 'test1234'))
+ assert_success capture
+ end
+
def test_purchase_with_skip_mpi_data
options = {
reference: '345123',
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 516435e58ba..436c19cb223 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -340,6 +340,14 @@ def test_failed_capture
assert_failure response
end
+ def test_successful_capture_with_shopper_statement
+ stub_comms do
+ @gateway.capture(@amount, '7914775043909934', @options.merge(shopper_statement: 'test1234'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal 'test1234', JSON.parse(data)['additionalData']['shopperStatement']
+ end.respond_with(successful_capture_response)
+ end
+
def test_successful_purchase_with_credit_card
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options)
From 88f127544ffa4d3294848249931308033a3bba79 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Tue, 14 Mar 2023 14:38:50 -0700
Subject: [PATCH 044/390] Checkout_v2: update idmepotency_key names
---
CHANGELOG | 3 ++-
lib/active_merchant/billing/gateways/checkout_v2.rb | 2 +-
test/remote/gateways/remote_checkout_v2_test.rb | 2 +-
test/unit/gateways/checkout_v2_test.rb | 2 +-
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1200d69303e..5d00ead558f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -82,7 +82,8 @@
* Ebanx: Add transaction inquire request [almalee24] #4725
* Ebanx: Add support for Elo & Hipercard [almalee24] #4702
* CheckoutV2: Add Idempotency key support [yunnydang] #4728
-* Adyen: Add support for shopper_statement field for capture [yunnydang] #PR 4736
+* Adyen: Add support for shopper_statement field for capture [yunnydang] #4736
+* CheckoutV2: Update idempotency_key name [yunnydang] #4737
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 34fcbcf2165..08d79b03a81 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -361,7 +361,7 @@ def headers(action, options)
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
- headers['Cko-Idempotency-Key'] = options[:cko_idempotency_key] if options[:cko_idempotency_key]
+ headers['Cko-Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
headers
end
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 1e9ad0fbfdf..85ee613e717 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -821,7 +821,7 @@ def test_expired_card_returns_error_code
end
def test_successful_purchase_with_idempotency_key
- response = @gateway.purchase(@amount, @credit_card, @options.merge(cko_idempotency_key: 'test123'))
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: 'test123'))
assert_success response
assert_equal 'Succeeded', response.message
end
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 882f5291d84..a1c04453366 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -437,7 +437,7 @@ def test_successful_purchase_with_metadata
def test_optional_idempotency_key_header
stub_comms(@gateway, :ssl_request) do
options = {
- cko_idempotency_key: 'test123'
+ idempotency_key: 'test123'
}
@gateway.purchase(@amount, @credit_card, options)
end.check_request do |_method, _url, _data, headers|
From c13dc313ca5a4778523de7350274a757f302e5cf Mon Sep 17 00:00:00 2001
From: Johan Herrera
Date: Mon, 27 Feb 2023 10:16:37 -0500
Subject: [PATCH 045/390] * Payeezy: Enable external 3DS
Summary:
------------------------------
This PR includes the fields and logic required to send external 3ds data
for purchases and auths.
Closes #4715
Test Execution:
------------------------------
Unit test
Finished in 0.067186 seconds.
46 tests, 211 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
Finished in 140.523393 seconds.
48 tests, 194 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Failures not related with the added code
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/payeezy.rb | 22 ++++++++++
test/remote/gateways/remote_payeezy_test.rb | 42 +++++++++++++++++++
3 files changed, 65 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 5d00ead558f..4493c25b95f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -84,6 +84,7 @@
* CheckoutV2: Add Idempotency key support [yunnydang] #4728
* Adyen: Add support for shopper_statement field for capture [yunnydang] #4736
* CheckoutV2: Update idempotency_key name [yunnydang] #4737
+* Payeezy: Enable external 3DS [jherreraa] #4715
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index b724cf3a14a..b08e2093db3 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -41,6 +41,7 @@ def purchase(amount, payment_method, options = {})
add_soft_descriptors(params, options)
add_level2_data(params, options)
add_stored_credentials(params, options)
+ add_external_three_ds(params, payment_method, options)
commit(params, options)
end
@@ -56,6 +57,7 @@ def authorize(amount, payment_method, options = {})
add_soft_descriptors(params, options)
add_level2_data(params, options)
add_stored_credentials(params, options)
+ add_external_three_ds(params, payment_method, options)
commit(params, options)
end
@@ -140,6 +142,26 @@ def scrub(transcript)
private
+ def add_external_three_ds(params, payment_method, options)
+ return unless three_ds = options[:three_d_secure]
+
+ params[:'3DS'] = {
+ program_protocol: three_ds[:version][0],
+ directory_server_transaction_id: three_ds[:ds_transaction_id],
+ cardholder_name: payment_method.name,
+ card_number: payment_method.number,
+ exp_date: format_exp_date(payment_method.month, payment_method.year),
+ cvv: payment_method.verification_value,
+ xid: three_ds[:acs_transaction_id],
+ cavv: three_ds[:cavv],
+ wallet_provider_id: 'NO_WALLET',
+ type: 'D'
+ }.compact
+
+ params[:eci_indicator] = options[:three_d_secure][:eci]
+ params[:method] = '3DS'
+ end
+
def add_invoice(params, options)
params[:merchant_ref] = options[:order_id]
end
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
index 8597009759b..177511fa45c 100644
--- a/test/remote/gateways/remote_payeezy_test.rb
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -171,6 +171,48 @@ def test_failed_purchase_with_insufficient_funds
assert_match(/Insufficient Funds/, response.message)
end
+ def test_successful_purchase_with_three_ds_data
+ @options[:three_d_secure] = {
+ version: '1',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_equal '100', response.params['bank_resp_code']
+ assert_equal nil, response.error_code
+ assert_success response
+ end
+
+ def test_authorize_and_capture_three_ds_data
+ @options[:three_d_secure] = {
+ version: '1',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_purchase_with_three_ds_version_data
+ @options[:three_d_secure] = {
+ version: '1.0.2',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_equal '100', response.params['bank_resp_code']
+ assert_equal nil, response.error_code
+ assert_success response
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
From fc742d9f237da011ead018c21c1213aa2a82ee41 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Wed, 22 Mar 2023 11:49:56 -0400
Subject: [PATCH 046/390] Shift4: Fix `Content-type` value (#4740)
Change `Content-type` value to `applicaiton/json` instead of xml
Unit: 23 tests, 147 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 24 tests, 56 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.6667% passed
---
lib/active_merchant/billing/gateways/shift4.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 7d638542355..4418c802752 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -306,7 +306,7 @@ def get_invoice(authorization)
def request_headers(action, options)
headers = {
- 'Content-Type' => 'application/x-www-form-urlencoded'
+ 'Content-Type' => 'application/json'
}
headers['AccessToken'] = @access_token
headers['Invoice'] = options[:invoice] if action != 'capture' && options[:invoice].present?
From 2aff17038638d3d597c6c8bddf329faa0164c65d Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 3 Apr 2023 08:52:09 -0400
Subject: [PATCH 047/390] Ebanx: Remove default email
ECS-2829
Ebanx requires that merchants pass in an email in
order to complete a transaction. Previously, ActiveMerchant
was sending in a default email if one was not provided which
causes Ebanx's fraud detection to mark these transactions as
failed for fraud. This incorrect failure message has led to
merchant frustration since they could not quickly know the
root of the problem
Test Summary
Remote:
32 tests, 88 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96.875% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ebanx.rb | 2 +-
test/remote/gateways/remote_ebanx_test.rb | 10 +++++++++-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 4493c25b95f..b3243f32358 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -85,6 +85,7 @@
* Adyen: Add support for shopper_statement field for capture [yunnydang] #4736
* CheckoutV2: Update idempotency_key name [yunnydang] #4737
* Payeezy: Enable external 3DS [jherreraa] #4715
+* Ebanx: Remove default email [aenand] #4747
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index b907855d50a..d0381245158 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -155,7 +155,7 @@ def add_authorization(post, authorization)
def add_customer_data(post, payment, options)
post[:payment][:name] = customer_name(payment, options)
- post[:payment][:email] = options[:email] || 'unspecified@example.com'
+ post[:payment][:email] = options[:email]
post[:payment][:document] = options[:document]
post[:payment][:birth_date] = options[:birth_date] if options[:birth_date]
end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
index 324a7d87f86..4ac5176ce55 100644
--- a/test/remote/gateways/remote_ebanx_test.rb
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -24,7 +24,8 @@ def setup
metadata_2: 'test2'
},
tags: EbanxGateway::TAGS,
- soft_descriptor: 'ActiveMerchant'
+ soft_descriptor: 'ActiveMerchant',
+ email: 'neymar@test.com'
}
@hiper_card = credit_card('6062825624254001')
@@ -133,6 +134,13 @@ def test_failed_authorize
assert_equal 'NOK', response.error_code
end
+ def test_failed_authorize_no_email
+ response = @gateway.authorize(@amount, @declined_card, @options.except(:email))
+ assert_failure response
+ assert_equal 'Field payment.email is required', response.message
+ assert_equal 'BP-DR-15', response.error_code
+ end
+
def test_successful_partial_capture_when_include_capture_amount_is_not_passed
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
From 35b56f5f291a76f437435994e5975fe129f5f336 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Wed, 22 Feb 2023 17:33:53 -0500
Subject: [PATCH 048/390] CyberSourceRest: Add stored credentials support
Description
-------------------------
This commit adds support for stored credentials to the CyberSourceRest gateway
and according to their docs CyberSource has two type of flows
[initial](https://developer.cybersource.com/docs/cybs/en-us/payments/developer/ctv/rest/payments/credentials-intro/credentials-maxtrix/credentials-maxtrix-initial.html) and [subsequent](https://developer.cybersource.com/docs/cybs/en-us/payments/developer/ctv/rest/payments/credentials-matrix/credentials-matrix-sub.html)
Closes #4707
Unit test
-------------------------
Finished in 0.025301 seconds.
18 tests, 97 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
711.43 tests/s, 3833.84 assertions/s
Remote test
-------------------------
Finished in 25.932718 seconds.
29 tests, 107 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
1.12 tests/s, 4.13 assertions/s
Rubocop
-------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 60 ++++++++++++++-
.../gateways/remote_cyber_source_rest_test.rb | 77 ++++++++++++++++++-
test/test_helper.rb | 1 +
test/unit/gateways/cyber_source_rest_test.rb | 41 ++++++++++
5 files changed, 178 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index b3243f32358..65b5cc20f50 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -86,6 +86,7 @@
* CheckoutV2: Update idempotency_key name [yunnydang] #4737
* Payeezy: Enable external 3DS [jherreraa] #4715
* Ebanx: Remove default email [aenand] #4747
+* CyberSourceRest: Add stored credentials support [jherreraa] #4707
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 69f4fbd7663..2dd1c063274 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -114,6 +114,7 @@ def build_auth_request(amount, payment, options)
add_amount(post, amount)
add_address(post, payment, options[:billing_address], options, :billTo)
add_address(post, payment, options[:shipping_address], options, :shipTo)
+ add_stored_credentials(post, payment, options)
end.compact
end
@@ -156,7 +157,6 @@ def add_reversal_amount(post, amount)
def add_amount(post, amount)
currency = options[:currency] || currency(amount)
-
post[:orderInformation][:amountDetails] = {
totalAmount: localized_amount(amount, currency),
currency: currency
@@ -251,6 +251,63 @@ def add_merchant_description(post, options)
merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality]
end
+ def add_stored_credentials(post, payment, options)
+ return unless stored_credential = options[:stored_credential]
+
+ options = stored_credential_options(stored_credential, options.fetch(:reason_code, ''))
+ post[:processingInformation][:commerceIndicator] = options.fetch(:transaction_type, 'internet')
+ stored_credential[:initial_transaction] ? initial_transaction(post, options) : subsequent_transaction(post, options)
+ end
+
+ def stored_credential_options(options, reason_code)
+ transaction_type = options[:reason_type]
+ transaction_type = 'install' if transaction_type == 'installment'
+ initiator = options[:initiator] if options[:initiator]
+ initiator = 'customer' if initiator == 'cardholder'
+ stored_on_file = options[:reason_type] == 'recurring'
+ options.merge({
+ transaction_type: transaction_type,
+ initiator: initiator,
+ reason_code: reason_code,
+ stored_on_file: stored_on_file
+ })
+ end
+
+ def add_processing_information(initiator, merchant_initiated_transaction_hash = {})
+ {
+ authorizationOptions: {
+ initiator: {
+ type: initiator,
+ merchantInitiatedTransaction: merchant_initiated_transaction_hash,
+ storedCredentialUsed: true
+ }
+ }
+ }.compact
+ end
+
+ def initial_transaction(post, options)
+ processing_information = add_processing_information(options[:initiator], {
+ reason: options[:reason_code]
+ })
+
+ post[:processingInformation].merge!(processing_information)
+ end
+
+ def subsequent_transaction(post, options)
+ network_transaction_id = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id) || ''
+ processing_information = add_processing_information(options[:initiator], {
+ originalAuthorizedAmount: post.dig(:orderInformation, :amountDetails, :totalAmount),
+ previousTransactionID: network_transaction_id,
+ reason: options[:reason_code],
+ storedCredentialUsed: options[:stored_on_file]
+ })
+ post[:processingInformation].merge!(processing_information)
+ end
+
+ def network_transaction_id_from(response)
+ response.dig('processorInformation', 'networkTransactionId')
+ end
+
def url(action)
"#{(test? ? test_url : live_url)}/pts/v2/#{action}"
end
@@ -272,6 +329,7 @@ def commit(action, post)
authorization: authorization_from(response),
avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')),
# cvv_result: CVVResult.new(response['some_cvv_response_key']),
+ network_transaction_id: network_transaction_id_from(response),
test: test?,
error_code: error_code_from(response)
)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index ccd06431234..36e7a47c9a6 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -13,6 +13,9 @@ def setup
month: 12,
year: 2031)
+ @master_card = credit_card('2222420000001113', brand: 'master')
+ @discover_card = credit_card('6011111111111117', brand: 'discover')
+
@apple_pay = network_tokenization_credit_card(
'4111111111111111',
payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
@@ -97,7 +100,6 @@ def test_handle_credentials_error
def test_successful_authorize
response = @gateway.authorize(@amount, @visa_card, @options)
-
assert_success response
assert response.test?
assert_equal 'AUTHORIZED', response.message
@@ -348,4 +350,77 @@ def test_failed_authorize_with_bank_account_missing_country_code
assert_failure response
assert_equal 'Declined - The request is missing one or more fields', response.params['message']
end
+
+ def stored_credential_options(*args, ntid: nil)
+ @options.merge(stored_credential: stored_credential(*args, network_transaction_id: ntid))
+ end
+
+ def test_purchase_using_stored_credential_initial_mit
+ options = stored_credential_options(:merchant, :internet, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ assert purchase = @gateway.purchase(@amount, @visa_card, options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_cit
+ options = stored_credential_options(:cardholder, :recurring, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:cardholder, :recurring, ntid: auth.network_transaction_id)
+ used_store_credentials[:reason_code] = '4'
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit
+ options = stored_credential_options(:merchant, :recurring, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:merchant, :recurring, ntid: auth.network_transaction_id)
+ used_store_credentials[:reason_code] = '4'
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_installment_cit
+ options = stored_credential_options(:cardholder, :installment, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:cardholder, :installment, ntid: auth.network_transaction_id)
+ used_store_credentials[:reason_code] = '4'
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_installment_mit
+ options = stored_credential_options(:merchant, :installment, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:merchant, :installment, ntid: auth.network_transaction_id)
+ used_store_credentials[:reason_code] = '4'
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_failure_stored_credential_invalid_reason_code
+ options = stored_credential_options(:cardholder, :internet, :initial)
+ assert auth = @gateway.authorize(@amount, @master_card, options)
+ assert_equal(auth.params['status'], 'INVALID_REQUEST')
+ assert_equal(auth.params['message'], 'Declined - One or more fields in the request contains invalid data')
+ assert_equal(auth.params['details'].first['field'], 'processingInformation.authorizationOptions.initiator.merchantInitiatedTransaction.reason')
+ end
+
+ def test_auth_and_purchase_with_network_txn_id
+ options = stored_credential_options(:merchant, :recurring, :initial)
+ options[:reason_code] = '4'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert purchase = @gateway.purchase(@amount, @visa_card, options.merge(network_transaction_id: auth.network_transaction_id))
+ assert_success purchase
+ end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ab8a5b251ba..27563086990 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -270,6 +270,7 @@ def stored_credential(*args, **options)
stored_credential[:reason_type] = 'recurring' if args.include?(:recurring)
stored_credential[:reason_type] = 'unscheduled' if args.include?(:unscheduled)
stored_credential[:reason_type] = 'installment' if args.include?(:installment)
+ stored_credential[:reason_type] = 'internet' if args.include?(:internet)
stored_credential[:initiator] = 'cardholder' if args.include?(:cardholder)
stored_credential[:initiator] = 'merchant' if args.include?(:merchant)
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 270fcf6a262..115d50674b9 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -233,6 +233,47 @@ def test_url_building
assert_equal "#{@gateway.class.test_url}/pts/v2/action", @gateway.send(:url, 'action')
end
+ def test_stored_credential_cit_initial
+ @options[:stored_credential] = stored_credential(:cardholder, :internet, :initial)
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_equal 'customer', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_stored_credential_recurring_cit
+ @options[:stored_credential] = stored_credential(:cardholder, :recurring)
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'recurring', request['processingInformation']['commerceIndicator']
+ assert_equal 'customer', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ assert_equal true, request.dig('processingInformation', 'authorizationOptions', 'initiator', 'merchantInitiatedTransaction', 'storedCredentialUsed')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_stored_credential_recurring_mit_ntid
+ @options[:stored_credential] = stored_credential(:merchant, :recurring, ntid: '123456789619999')
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'recurring', request['processingInformation']['commerceIndicator']
+ assert_equal 'merchant', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ assert_equal true, request.dig('processingInformation', 'authorizationOptions', 'initiator', 'merchantInitiatedTransaction', 'storedCredentialUsed')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
private
def parse_signature(signature)
From ee712d9d30bba4d9c7aab8136853180259251b31 Mon Sep 17 00:00:00 2001
From: naashton
Date: Fri, 24 Mar 2023 10:39:17 -0400
Subject: [PATCH 049/390] Payeezy: Add `last_name` for
`add_network_tokenization`
This change updates the `add_network_tokenization` method to include the
`last_name` in the `cardholder_name` value when getting the name from a
payment method
Closes #4743
Unit: 49 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 50 tests, 201 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/payeezy.rb | 10 ++++++++--
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 65b5cc20f50..9cce6f9011a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -87,6 +87,7 @@
* Payeezy: Enable external 3DS [jherreraa] #4715
* Ebanx: Remove default email [aenand] #4747
* CyberSourceRest: Add stored credentials support [jherreraa] #4707
+* Payeezy: Add `last_name` for `add_network_tokenization` [naashton] #4743
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index b08e2093db3..d8f685d2f71 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -219,7 +219,7 @@ def add_echeck(params, echeck, options)
tele_check[:check_type] = 'P'
tele_check[:routing_number] = echeck.routing_number
tele_check[:account_number] = echeck.account_number
- tele_check[:accountholder_name] = "#{echeck.first_name} #{echeck.last_name}"
+ tele_check[:accountholder_name] = name_from_payment_method(echeck)
tele_check[:customer_id_type] = options[:customer_id_type] if options[:customer_id_type]
tele_check[:customer_id_number] = options[:customer_id_number] if options[:customer_id_number]
tele_check[:client_email] = options[:client_email] if options[:client_email]
@@ -265,7 +265,7 @@ def add_card_data(payment_method)
def add_network_tokenization(params, payment_method, options)
nt_card = {}
nt_card[:type] = 'D'
- nt_card[:cardholder_name] = payment_method.first_name || name_from_address(options)
+ nt_card[:cardholder_name] = name_from_payment_method(payment_method) || name_from_address(options)
nt_card[:card_number] = payment_method.number
nt_card[:exp_date] = format_exp_date(payment_method.month, payment_method.year)
nt_card[:cvv] = payment_method.verification_value
@@ -287,6 +287,12 @@ def name_from_address(options)
return address[:name] if address[:name]
end
+ def name_from_payment_method(payment_method)
+ return unless payment_method.first_name && payment_method.last_name
+
+ return "#{payment_method.first_name} #{payment_method.last_name}"
+ end
+
def add_address(params, options)
address = options[:billing_address]
return unless address
From b15665615d91e444a8b18d2e92c251e66531fa6f Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 3 Apr 2023 13:01:26 -0400
Subject: [PATCH 050/390] Stripe PI: tokenize PM for verify
ECS-2867
For the Stripe PI gateway, when a merchant runs a verify
they want to get back the resulting card information that
Stripe provides. In off_session cases we are not tokenizing
the card at Stripe which prevents us from getting back
valuable card details.
This commit updates the logic to always get the card details
back from Stripe on verify transactions.
This commit also improves the resiliency of Stripe PI remote tests
by dynamically creating a customer object before running the remote
tests so that they do not error out with a too many payment
methods for customer message.
Test Summary
Remote:
80 tests, 380 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/stripe_payment_intents.rb | 9 ++++++++-
.../gateways/remote_stripe_payment_intents_test.rb | 5 +++--
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9cce6f9011a..024a8075cdb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -88,6 +88,7 @@
* Ebanx: Remove default email [aenand] #4747
* CyberSourceRest: Add stored credentials support [jherreraa] #4707
* Payeezy: Add `last_name` for `add_network_tokenization` [naashton] #4743
+* Stripe PI: Tokenize payment method at Stripe for `verify` [aenand] #4748
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 989078dd9a8..832908430a5 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -56,6 +56,11 @@ def show_intent(intent_id, options)
commit(:get, "payment_intents/#{intent_id}", nil, options)
end
+ def create_test_customer
+ response = api_request(:post, 'customers')
+ response['id']
+ end
+
def confirm_intent(intent_id, payment_method, options = {})
post = {}
result = add_payment_method_token(post, payment_method, options)
@@ -236,7 +241,7 @@ def unstore(identification, options = {}, deprecated_options = {})
end
def verify(payment_method, options = {})
- create_setup_intent(payment_method, options.merge!(confirm: true))
+ create_setup_intent(payment_method, options.merge!({ confirm: true, verify: true }))
end
def setup_purchase(money, options = {})
@@ -322,6 +327,8 @@ def add_payment_method_token(post, payment_method, options, responses = [])
when String
extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
when ActiveMerchant::Billing::CreditCard
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) if options[:verify]
+
get_payment_method_data_from_card(post, payment_method, options, responses)
end
end
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 01625e4cc83..346af9f4f67 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -3,7 +3,7 @@
class RemoteStripeIntentsTest < Test::Unit::TestCase
def setup
@gateway = StripePaymentIntentsGateway.new(fixtures(:stripe))
- @customer = fixtures(:stripe_verified_bank_account)[:customer_id]
+ @customer = @gateway.create_test_customer
@amount = 2000
@three_ds_payment_method = 'pm_card_threeDSecure2Required'
@visa_payment_method = 'pm_card_visa'
@@ -1191,7 +1191,8 @@ def test_successful_verify
options = {
customer: @customer
}
- assert verify = @gateway.verify(@visa_payment_method, options)
+ assert verify = @gateway.verify(@visa_card, options)
+ assert_equal 'US', verify.responses[0].params.dig('card', 'country')
assert_equal 'succeeded', verify.params['status']
end
From 1e9c672e14d4db142cbfb7b044bf35d270a940d2 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Fri, 7 Apr 2023 15:27:20 -0700
Subject: [PATCH 051/390] Kushki Gateway: Add support for the months and
deferred fields
---
CHANGELOG | 1 +
.../billing/gateways/kushki.rb | 20 +++++++++++++++++++
test/remote/gateways/remote_kushki_test.rb | 6 +++++-
test/unit/gateways/kushki_test.rb | 10 +++++++++-
4 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 024a8075cdb..c43d523255f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -89,6 +89,7 @@
* CyberSourceRest: Add stored credentials support [jherreraa] #4707
* Payeezy: Add `last_name` for `add_network_tokenization` [naashton] #4743
* Stripe PI: Tokenize payment method at Stripe for `verify` [aenand] #4748
+* Kushki: Add support for the months and deferred fields [yunnydang] #4752
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index 6125ea41c35..c4101069450 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -83,6 +83,8 @@ def tokenize(amount, payment_method, options)
add_payment_method(post, payment_method, options)
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
commit(action, post)
end
@@ -96,6 +98,8 @@ def charge(amount, authorization, options)
add_contact_details(post, options[:contact_details]) if options[:contact_details]
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
commit(action, post)
end
@@ -108,6 +112,8 @@ def preauthorize(amount, authorization, options)
add_invoice(action, post, amount, options)
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
commit(action, post)
end
@@ -184,6 +190,20 @@ def add_metadata(post, options)
post[:metadata] = options[:metadata] if options[:metadata]
end
+ def add_months(post, options)
+ post[:months] = options[:months] if options[:months]
+ end
+
+ def add_deferred(post, options)
+ return unless options[:deferred_grace_months] && options[:deferred_credit_type] && options[:deferred_months]
+
+ post[:deferred] = {
+ graceMonths: options[:deferred_grace_months],
+ creditType: options[:deferred_credit_type],
+ months: options[:deferred_months]
+ }
+ end
+
ENDPOINT = {
'tokenize' => 'tokens',
'charge' => 'charges',
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index 1fb1ad41a98..2e575a2f7ad 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -36,7 +36,11 @@ def test_successful_purchase_with_options
metadata: {
productos: 'bananas',
nombre_apellido: 'Kirk'
- }
+ },
+ months: 2,
+ deferred_grace_months: '05',
+ deferred_credit_type: '01',
+ deferred_months: 3
}
amount = 100 * (
diff --git a/test/unit/gateways/kushki_test.rb b/test/unit/gateways/kushki_test.rb
index b13d04653f5..f623b6b4ec9 100644
--- a/test/unit/gateways/kushki_test.rb
+++ b/test/unit/gateways/kushki_test.rb
@@ -41,7 +41,11 @@ def test_successful_purchase_with_options
metadata: {
productos: 'bananas',
nombre_apellido: 'Kirk'
- }
+ },
+ months: 2,
+ deferred_grace_months: '05',
+ deferred_credit_type: '01',
+ deferred_months: 3
}
amount = 100 * (
@@ -58,6 +62,10 @@ def test_successful_purchase_with_options
@gateway.purchase(amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_includes data, 'metadata'
+ assert_includes data, 'months'
+ assert_includes data, 'deferred_grace_month'
+ assert_includes data, 'deferred_credit_type'
+ assert_includes data, 'deferred_months'
end.respond_with(successful_token_response, successful_charge_response)
assert_success response
From 71c88cee0e497a88f77dad5dc184cb099dcb45ef Mon Sep 17 00:00:00 2001
From: aenand
Date: Thu, 6 Apr 2023 15:18:27 -0400
Subject: [PATCH 052/390] Borgun: Update TrCurrencyExponent
ECS-2861
A merchant using the Borgun gateway reported that when
a user was completing the challenge, the gateway was displaying
a value 100 times greater than what was requested. This casused
the ccardholders to stop the 3DS flow and abandon the cart.
After reaching out to the Borgun gateway they explained that
the ISK currency on Borgun is sometimes a 0 decimal currency
and sometimes a 2 decimal currency. The explanation given
via email is that we must provide the TrCurrencyExponent
of 2 when utilizing the 3DS flow but not on the finish sale
portion.
Remote:
22 tests, 47 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
90.9091% passed
Note: these 2 tests fail on master
---
CHANGELOG | 1 +
.../billing/gateways/borgun.rb | 7 ++++-
test/remote/gateways/remote_borgun_test.rb | 26 +++++++++----------
test/unit/gateways/borgun_test.rb | 2 ++
4 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c43d523255f..84cc18822e6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -90,6 +90,7 @@
* Payeezy: Add `last_name` for `add_network_tokenization` [naashton] #4743
* Stripe PI: Tokenize payment method at Stripe for `verify` [aenand] #4748
* Kushki: Add support for the months and deferred fields [yunnydang] #4752
+* Borgun: Update TrCurrencyExponent for 3DS transactions with `ISK` [aenand] #4751
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
index 8d4883dd4f7..a2681c59200 100644
--- a/lib/active_merchant/billing/gateways/borgun.rb
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -111,7 +111,12 @@ def add_3ds_preauth_fields(post, options)
def add_invoice(post, money, options)
post[:TrAmount] = amount(money)
post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
- post[:TrCurrencyExponent] = options[:currency_exponent] || 0 if options[:apply_3d_secure] == '1'
+ # The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
+ if post[:TrCurrency] == '352' && options[:apply_3d_secure] == '1'
+ post[:TrCurrencyExponent] = 2
+ else
+ post[:TrCurrencyExponent] = 0
+ end
post[:TerminalID] = options[:terminal_id] || '1'
end
diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb
index 4b6771917dd..c6f2b5afbdb 100644
--- a/test/remote/gateways/remote_borgun_test.rb
+++ b/test/remote/gateways/remote_borgun_test.rb
@@ -187,19 +187,19 @@ def test_failed_void
# This test does not consistently pass. When run multiple times within 1 minute,
# an ActiveMerchant::ConnectionError()
# exception is raised.
- def test_invalid_login
- gateway = BorgunGateway.new(
- processor: '0',
- merchant_id: '0',
- username: 'not',
- password: 'right'
- )
- authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 401 [ISS.0084.9001] Invalid credentials' do
- gateway.purchase(@amount, @credit_card, @options)
- end
- assert response = authentication_exception.response
- assert_match(/Access Denied/, response.body)
- end
+ # def test_invalid_login
+ # gateway = BorgunGateway.new(
+ # processor: '0',
+ # merchant_id: '0',
+ # username: 'not',
+ # password: 'right'
+ # )
+ # authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 401 [ISS.0084.9001] Invalid credentials' do
+ # gateway.purchase(@amount, @credit_card, @options)
+ # end
+ # assert response = authentication_exception.response
+ # assert_match(/Access Denied/, response.body)
+ # end
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb
index fbe77e3595c..0bfc0dc51ed 100644
--- a/test/unit/gateways/borgun_test.rb
+++ b/test/unit/gateways/borgun_test.rb
@@ -62,6 +62,7 @@ def test_successful_preauth_3ds
end.check_request do |_endpoint, data, _headers|
assert_match(/MerchantReturnURL>#{@options[:merchant_return_url]}/, data)
assert_match(/SaleDescription>#{@options[:sale_description]}/, data)
+ assert_match(/TrCurrencyExponent>2/, data)
end.respond_with(successful_get_3ds_authentication_response)
assert_success response
@@ -76,6 +77,7 @@ def test_successful_purchase_after_3ds
@gateway.purchase(@amount, @credit_card, @options.merge({ three_ds_message_id: '98324_zzi_1234353' }))
end.check_request do |_endpoint, data, _headers|
assert_match(/ThreeDSMessageId>#{@options[:three_ds_message_id]}/, data)
+ assert_match(/TrCurrencyExponent>0/, data)
end.respond_with(successful_purchase_response)
assert_success response
From 71f35059da5206225ce5dc33e0a043c65e4301e7 Mon Sep 17 00:00:00 2001
From: Johan Herrera
Date: Wed, 29 Mar 2023 16:15:37 -0500
Subject: [PATCH 053/390] CyberSourceRest: Add gateway specific fields handling
Summary:
In order to handle several gateway specific fields this commit add the following ones in the cybersource rest gateway file
- ignore_avs
- ignore_cvv
- mdd_fields
- reconciliation_id
- customer_id
- zero_amount_verify
- sec_code
Closes #4746
Remote Test:
Finished in 36.507289 seconds.
35 tests, 108 assertions, 0 failures, 0 errors, 0 pendings,0 omissions, 0 notifications 100% passed
Unit Tests:
Finished in 0.06123 seconds.
2718 tests, 150 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed
RuboCop:
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 58 ++++-
.../gateways/remote_cyber_source_rest_test.rb | 48 ++++-
test/unit/gateways/cyber_source_rest_test.rb | 198 ++++++++++++++++++
4 files changed, 300 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 84cc18822e6..2ebaa5e2a31 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -91,6 +91,7 @@
* Stripe PI: Tokenize payment method at Stripe for `verify` [aenand] #4748
* Kushki: Add support for the months and deferred fields [yunnydang] #4752
* Borgun: Update TrCurrencyExponent for 3DS transactions with `ISK` [aenand] #4751
+* CyberSourceRest: Add gateway specific fields handling [jherreraa] #4746
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 2dd1c063274..7f6441cbeed 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -49,20 +49,20 @@ def authorize(money, payment, options = {}, capture = false)
post = build_auth_request(money, payment, options)
post[:processingInformation][:capture] = true if capture
- commit('payments', post)
+ commit('payments', post, options)
end
def capture(money, authorization, options = {})
payment = authorization.split('|').first
post = build_reference_request(money, options)
- commit("payments/#{payment}/captures", post)
+ commit("payments/#{payment}/captures", post, options)
end
def refund(money, authorization, options = {})
payment = authorization.split('|').first
post = build_reference_request(money, options)
- commit("payments/#{payment}/refunds", post)
+ commit("payments/#{payment}/refunds", post, options)
end
def credit(money, payment, options = {})
@@ -111,9 +111,12 @@ def build_auth_request(amount, payment, options)
add_customer_id(post, options)
add_code(post, options)
add_payment(post, payment, options)
+ add_mdd_fields(post, options)
add_amount(post, amount)
add_address(post, payment, options[:billing_address], options, :billTo)
add_address(post, payment, options[:shipping_address], options, :shipTo)
+ add_business_rules_data(post, payment, options)
+ add_partner_solution_id(post)
add_stored_credentials(post, payment, options)
end.compact
end
@@ -121,7 +124,9 @@ def build_auth_request(amount, payment, options)
def build_reference_request(amount, options)
{ clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
add_code(post, options)
+ add_mdd_fields(post, options)
add_amount(post, amount)
+ add_partner_solution_id(post)
end.compact
end
@@ -129,6 +134,7 @@ def build_credit_request(amount, payment, options)
{ clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
add_code(post, options)
add_credit_card(post, payment)
+ add_mdd_fields(post, options)
add_amount(post, amount)
add_address(post, payment, options[:billing_address], options, :billTo)
add_merchant_description(post, options)
@@ -320,7 +326,10 @@ def parse(body)
JSON.parse(body)
end
- def commit(action, post)
+ def commit(action, post, options = {})
+ add_reconciliation_id(post, options)
+ add_sec_code(post, options)
+ add_invoice_number(post, options)
response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post)))
Response.new(
success_from(response),
@@ -401,6 +410,47 @@ def auth_headers(action, post, http_method = 'post')
'Digest' => digest
}
end
+
+ def add_business_rules_data(post, payment, options)
+ post[:processingInformation][:authorizationOptions] = {}
+ unless payment.is_a?(NetworkTokenizationCreditCard)
+ post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
+ post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
+ end
+ end
+
+ def add_mdd_fields(post, options)
+ mdd_fields = options.select { |k, v| k.to_s.start_with?('mdd_field') && v.present? }
+ return unless mdd_fields.present?
+
+ post[:merchantDefinedInformation] = mdd_fields.map do |key, value|
+ { key: key, value: value }
+ end
+ end
+
+ def add_reconciliation_id(post, options)
+ return unless options[:reconciliation_id].present?
+
+ post[:clientReferenceInformation][:reconciliationId] = options[:reconciliation_id]
+ end
+
+ def add_sec_code(post, options)
+ return unless options[:sec_code].present?
+
+ post[:processingInformation][:bankTransferOptions] = { secCode: options[:sec_code] }
+ end
+
+ def add_invoice_number(post, options)
+ return unless options[:invoice_number].present?
+
+ post[:orderInformation][:invoiceDetails] = { invoiceNumber: options[:invoice_number] }
+ end
+
+ def add_partner_solution_id(post)
+ return unless application_id
+
+ post[:clientReferenceInformation][:partner] = { solutionId: application_id }
+ end
end
end
end
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 36e7a47c9a6..51ebe1a44e4 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -87,7 +87,16 @@ def setup
@options = {
order_id: generate_unique_id,
currency: 'USD',
- email: 'test@cybs.com'
+ email: 'test@cybs.com',
+ billing_address: {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ }
}
end
@@ -423,4 +432,41 @@ def test_auth_and_purchase_with_network_txn_id
assert purchase = @gateway.purchase(@amount, @visa_card, options.merge(network_transaction_id: auth.network_transaction_id))
assert_success purchase
end
+
+ def test_successful_purchase_with_reconciliation_id
+ options = @options.merge(reconciliation_id: '1936831')
+ assert response = @gateway.purchase(@amount, @visa_card, options)
+ assert_success response
+ end
+
+ def test_successful_authorization_with_reconciliation_id
+ options = @options.merge(reconciliation_id: '1936831')
+ assert response = @gateway.authorize(@amount, @visa_card, options)
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_verify_zero_amount
+ @options[:zero_amount_auth] = true
+ response = @gateway.verify(@visa_card, @options)
+ assert_success response
+ assert_match '0.00', response.params['orderInformation']['amountDetails']['authorizedAmount']
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_bank_account_purchase_with_sec_code
+ options = @options.merge(sec_code: 'WEB')
+ response = @gateway.purchase(@amount, @bank_account, options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_purchase_with_solution_id
+ ActiveMerchant::Billing::CyberSourceRestGateway.application_id = 'A1000000'
+ assert response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ ensure
+ ActiveMerchant::Billing::CyberSourceGateway.application_id = nil
+ end
end
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 115d50674b9..cbafb59e562 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -274,6 +274,124 @@ def test_stored_credential_recurring_mit_ntid
assert_success response
end
+ def test_successful_credit_card_purchase_single_request_ignore_avs
+ stub_comms do
+ options = @options.merge(ignore_avs: true)
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_equal json_body['processingInformation']['authorizationOptions']['ignoreAvsResult'], 'true'
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_without_ignore_avs
+ stub_comms do
+ # globally ignored AVS for gateway instance:
+ options = @options.merge(ignore_avs: false)
+ @gateway.options[:ignore_avs] = true
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_ignore_ccv
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: true))
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_equal json_body['processingInformation']['authorizationOptions']['ignoreCvResult'], 'true'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_without_ignore_ccv
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: false))
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_includes_mdd_fields
+ stub_comms do
+ @gateway.authorize(100, @credit_card, order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_capture_includes_mdd_fields
+ stub_comms do
+ @gateway.capture(100, '1846925324700976124593', order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_capture_response)
+ end
+
+ def test_credit_includes_mdd_fields
+ stub_comms do
+ @gateway.credit(@amount, @credit_card, mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_credit_response)
+ end
+
+ def test_authorize_includes_reconciliation_id
+ stub_comms do
+ @gateway.authorize(100, @credit_card, order_id: '1', reconciliation_id: '181537')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['clientReferenceInformation']['reconciliationId'], '181537'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_bank_account_purchase_includes_sec_code
+ stub_comms do
+ @gateway.purchase(@amount, @bank_account, order_id: '1', sec_code: 'WEB')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['processingInformation']['bankTransferOptions']['secCode'], 'WEB'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_includes_invoice_number
+ stub_comms do
+ @gateway.purchase(100, @credit_card, invoice_number: '1234567')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['orderInformation']['invoiceDetails']['invoiceNumber'], '1234567'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_adds_application_id_as_partner_solution_id
+ partner_id = 'partner_id'
+ CyberSourceRestGateway.application_id = partner_id
+
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['clientReferenceInformation']['partner']['solutionId'], partner_id
+ end.respond_with(successful_purchase_response)
+ ensure
+ CyberSourceRestGateway.application_id = nil
+ end
+
private
def parse_signature(signature)
@@ -381,4 +499,84 @@ def successful_purchase_response
}
RESPONSE
end
+
+ def successful_capture_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "void": {
+ "method": "POST",
+ "href": "/pts/v2/captures/6799471903876585704951/voids"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/captures/6799471903876585704951"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "TC50171_3"
+ },
+ "id": "6799471903876585704951",
+ "orderInformation": {
+ "amountDetails": {
+ "totalAmount": "102.21",
+ "currency": "USD"
+ }
+ },
+ "reconciliationId": "78243988SD9YL291",
+ "status": "PENDING",
+ "submitTimeUtc": "2023-03-27T19:59:50Z"
+ }
+ RESPONSE
+ end
+
+ def successful_credit_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "void": {
+ "method": "POST",
+ "href": "/pts/v2/credits/6799499091686234304951/voids"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/credits/6799499091686234304951"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "12345678"
+ },
+ "creditAmountDetails": {
+ "currency": "usd",
+ "creditAmount": "200.00"
+ },
+ "id": "6799499091686234304951",
+ "orderInformation": {
+ "amountDetails": {
+ "currency": "usd"
+ }
+ },
+ "paymentAccountInformation": {
+ "card": {
+ "type": "001"
+ }
+ },
+ "paymentInformation": {
+ "tokenizedCard": {
+ "type": "001"
+ },
+ "card": {
+ "type": "001"
+ }
+ },
+ "processorInformation": {
+ "approvalCode": "888888",
+ "responseCode": "100"
+ },
+ "reconciliationId": "70391830ZFKZI570",
+ "status": "PENDING",
+ "submitTimeUtc": "2023-03-27T20:45:09Z"
+ }
+ RESPONSE
+ end
end
From 3f995eb5dec0b04a563e2fa68128fe2cc557757b Mon Sep 17 00:00:00 2001
From: cristian
Date: Tue, 11 Apr 2023 16:33:04 -0500
Subject: [PATCH 054/390] IPG: Improve error handling
Summary:
------------------------------
This change improves the amount of detail in the response
message when the gateway responds with an error.
Closes #4753
Remote Test:
------------------------------
Remote:
18 tests, 54 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 29.462929 seconds
5483 tests, 77277 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ipg.rb | 2 +-
test/remote/gateways/remote_ipg_test.rb | 14 +++++++-------
test/unit/gateways/ipg_test.rb | 20 +++++++++++++++-----
4 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 2ebaa5e2a31..b9945b09f71 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -92,6 +92,7 @@
* Kushki: Add support for the months and deferred fields [yunnydang] #4752
* Borgun: Update TrCurrencyExponent for 3DS transactions with `ISK` [aenand] #4751
* CyberSourceRest: Add gateway specific fields handling [jherreraa] #4746
+* IPG: Improve error handling [heavyblade] #4753
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/ipg.rb b/lib/active_merchant/billing/gateways/ipg.rb
index 46052cfd1e7..128f9e18a1d 100644
--- a/lib/active_merchant/billing/gateways/ipg.rb
+++ b/lib/active_merchant/billing/gateways/ipg.rb
@@ -396,7 +396,7 @@ def parse_element(reply, node)
end
def message_from(response)
- response[:TransactionResult]
+ [response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
end
def authorization_from(action, response)
diff --git a/test/remote/gateways/remote_ipg_test.rb b/test/remote/gateways/remote_ipg_test.rb
index 8c797606e97..6b336d47780 100644
--- a/test/remote/gateways/remote_ipg_test.rb
+++ b/test/remote/gateways/remote_ipg_test.rb
@@ -5,9 +5,9 @@ def setup
@gateway = IpgGateway.new(fixtures(:ipg))
@amount = 100
- @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '530', month: '12', year: '2022')
+ @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '123', month: '12', year: '2029')
@declined_card = credit_card('4000300011112220', brand: 'mastercard', verification_value: '652', month: '12', year: '2022')
- @visa_card = credit_card('4704550000000005', brand: 'visa', verification_value: '123', month: '12', year: '2022')
+ @visa_card = credit_card('4704550000000005', brand: 'visa', verification_value: '123', month: '12', year: '2029')
@options = {
currency: 'ARS'
}
@@ -96,7 +96,7 @@ def test_successful_purchase_with_3ds2_options
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
assert_equal 'SGS-050005', response.error_code
end
@@ -121,14 +121,14 @@ def test_successful_authorize_and_capture
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_equal 'DECLINED, Do not honour', response.message
assert_equal 'SGS-050005', response.error_code
end
def test_failed_capture
response = @gateway.capture(@amount, '', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
assert_equal 'SGS-005001', response.error_code
end
@@ -159,7 +159,7 @@ def test_successful_refund
def test_failed_refund
response = @gateway.refund(@amount, '', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
assert_equal 'SGS-005001', response.error_code
end
@@ -172,7 +172,7 @@ def test_successful_verify
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
assert_equal 'SGS-050005', response.error_code
end
diff --git a/test/unit/gateways/ipg_test.rb b/test/unit/gateways/ipg_test.rb
index d92cea9daa0..867f2e9878a 100644
--- a/test/unit/gateways/ipg_test.rb
+++ b/test/unit/gateways/ipg_test.rb
@@ -173,7 +173,7 @@ def test_failed_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
end
def test_successful_authorize
@@ -194,7 +194,7 @@ def test_failed_authorize
response = @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: 'ORD03' }))
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_capture
@@ -215,7 +215,7 @@ def test_failed_capture
response = @gateway.capture(@amount, '123', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_refund
@@ -236,7 +236,7 @@ def test_failed_refund
response = @gateway.refund(@amount, '123', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_void
@@ -257,7 +257,7 @@ def test_failed_void
response = @gateway.void('', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_verify
@@ -332,6 +332,16 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_message_from_just_with_transaction_result
+ am_response = { TransactionResult: 'success !' }
+ assert_equal 'success !', @gateway.send(:message_from, am_response)
+ end
+
+ def test_message_from_with_an_error
+ am_response = { TransactionResult: 'DECLINED', ErrorMessage: 'CODE: this is an error message' }
+ assert_equal 'DECLINED, this is an error message', @gateway.send(:message_from, am_response)
+ end
+
private
def successful_purchase_response
From 50a09f913332f274ae36d930b880d0057ed7f4a2 Mon Sep 17 00:00:00 2001
From: cristian
Date: Tue, 28 Mar 2023 13:02:26 -0500
Subject: [PATCH 055/390] * Shift4: Handle access token failed calls
Summary:
------------------------------
Adding changes to handle failed calls to get the access token
GWS-46
Closes #4745
Remote Test:
------------------------------
Finished in 172.659123 seconds.
24 tests, 56 assertions, 2 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
91.6667% passed
Unit Tests:
------------------------------
Finished in 40.296092 seconds.
5480 tests, 77260 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/shift4.rb | 2 +
test/unit/gateways/shift4_test.rb | 58 +++++++++++++++----
3 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index b9945b09f71..ace3e7af4cc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -93,6 +93,7 @@
* Borgun: Update TrCurrencyExponent for 3DS transactions with `ISK` [aenand] #4751
* CyberSourceRest: Add gateway specific fields handling [jherreraa] #4746
* IPG: Improve error handling [heavyblade] #4753
+* Shift4: Handle access token failed calls [heavyblade] #4745
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 4418c802752..8e11769c121 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -153,6 +153,8 @@ def setup_access_token
add_datetime(post, options)
response = commit('accesstoken', post, request_headers('accesstoken', options))
+ raise ArgumentError, response.params.fetch('result', [{}]).first.dig('error', 'longText') unless response.success?
+
response.params['result'].first['credential']['accessToken']
end
diff --git a/test/unit/gateways/shift4_test.rb b/test/unit/gateways/shift4_test.rb
index 690b92bd487..0cddc546615 100644
--- a/test/unit/gateways/shift4_test.rb
+++ b/test/unit/gateways/shift4_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class Shift4Gateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class Shift4Test < Test::Unit::TestCase
include CommStub
def setup
@@ -365,6 +355,20 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_setup_access_token_should_rise_an_exception_under_unsuccessful_request
+ @gateway.expects(:ssl_post).returns(failed_auth_response)
+
+ assert_raises(ArgumentError) do
+ @gateway.setup_access_token
+ end
+ end
+
+ def test_setup_access_token_should_successfully_extract_the_token_from_response
+ @gateway.expects(:ssl_post).returns(sucess_auth_response)
+
+ assert_equal 'abc123', @gateway.setup_access_token
+ end
+
private
def response_result(response)
@@ -997,4 +1001,38 @@ def successful_access_token_response
}
RESPONSE
end
+
+ def failed_auth_response
+ <<-RESPONSE
+ {
+ "result": [
+ {
+ "error": {
+ "longText": "AuthToken not valid ENGINE22CE",
+ "primaryCode": 9862,
+ "secondaryCode": 4,
+ "shortText ": "AuthToken"
+ },
+ "server": {
+ "name": "UTGAPI03CE"
+ }
+ }
+ ]
+ }
+ RESPONSE
+ end
+
+ def sucess_auth_response
+ <<-RESPONSE
+ {
+ "result": [
+ {
+ "credential": {
+ "accessToken": "abc123"
+ }
+ }
+ ]
+ }
+ RESPONSE
+ end
end
From 3a8c637f4f0a8e22136c8a9305089e4028a43486 Mon Sep 17 00:00:00 2001
From: Willem Kappers
Date: Wed, 5 Apr 2023 13:38:46 -0400
Subject: [PATCH 056/390] Bogus: Add verify, plus assoc. test
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/bogus.rb | 4 ++++
test/unit/gateways/bogus_test.rb | 11 +++++++++++
3 files changed, 16 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index ace3e7af4cc..3a0da37adff 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -94,6 +94,7 @@
* CyberSourceRest: Add gateway specific fields handling [jherreraa] #4746
* IPG: Improve error handling [heavyblade] #4753
* Shift4: Handle access token failed calls [heavyblade] #4745
+* Bogus: Add verify functionality [willemk] #4749
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb
index 9aed8028586..30b8be9838a 100644
--- a/lib/active_merchant/billing/gateways/bogus.rb
+++ b/lib/active_merchant/billing/gateways/bogus.rb
@@ -90,6 +90,10 @@ def void(reference, options = {})
end
end
+ def verify(credit_card, options = {})
+ authorize(0, credit_card, options)
+ end
+
def store(paysource, options = {})
case normalize(paysource)
when /1$/
diff --git a/test/unit/gateways/bogus_test.rb b/test/unit/gateways/bogus_test.rb
index 4564a4d3096..301ed2ddfa6 100644
--- a/test/unit/gateways/bogus_test.rb
+++ b/test/unit/gateways/bogus_test.rb
@@ -96,6 +96,17 @@ def test_void
end
end
+ def test_verify
+ assert @gateway.verify(credit_card(CC_SUCCESS_PLACEHOLDER)).success?
+ response = @gateway.verify(credit_card(CC_FAILURE_PLACEHOLDER))
+ refute response.success?
+ assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ e = assert_raises(ActiveMerchant::Billing::Error) do
+ @gateway.verify(credit_card('123'))
+ end
+ assert_equal('Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error', e.message)
+ end
+
def test_store
assert @gateway.store(credit_card(CC_SUCCESS_PLACEHOLDER)).success?
response = @gateway.store(credit_card(CC_FAILURE_PLACEHOLDER))
From 4f42b812e52ce56f9ff3936d94ca8620a757e10f Mon Sep 17 00:00:00 2001
From: Nicolas Maalouf
Date: Fri, 14 Apr 2023 12:01:09 +0100
Subject: [PATCH 057/390] Checkout v2: Add Shipping Address
Add shipping address local and remote tests
Remove Marketplace object as not supported for Sandbox testing
---
CHANGELOG | 1 +
.../billing/gateways/checkout_v2.rb | 16 ++++++++
.../gateways/remote_checkout_v2_test.rb | 37 ++++++++++++-------
test/unit/gateways/checkout_v2_test.rb | 26 ++++++++++---
4 files changed, 62 insertions(+), 18 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3a0da37adff..a77db13c6ef 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
* Element: Include Apple Pay - Google pay methods [jherrera] #4647
* dLocal: Add transaction query API(s) request [almalee24] #4584
* MercadoPago: Add transaction inquire request [molbrown] #4588
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 08d79b03a81..ba9323d0d84 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -49,6 +49,7 @@ def capture(amount, authorization, options = {})
post[:capture_type] = options[:capture_type] || 'Final'
add_invoice(post, amount, options)
add_customer_data(post, options)
+ add_shipping_address(post, options)
add_metadata(post, options)
commit(:capture, post, options, authorization)
@@ -121,6 +122,7 @@ def store(payment_method, options = {})
add_payment_method(post, token, options)
post.merge!(post.delete(:source))
add_customer_data(post, options)
+ add_shipping_address(post, options)
r.process { commit(:store, post, options) }
end
end
@@ -137,6 +139,7 @@ def build_auth_or_purchase(post, amount, payment_method, options)
add_authorization_type(post, options)
add_payment_method(post, payment_method, options)
add_customer_data(post, options)
+ add_shipping_address(post, options)
add_stored_credential_options(post, options)
add_transaction_data(post, options)
add_3ds(post, options)
@@ -236,6 +239,19 @@ def add_customer_data(post, options)
end
end
+ def add_shipping_address(post, options)
+ if address = options[:shipping_address]
+ post[:shipping] = {}
+ post[:shipping][:address] = {}
+ post[:shipping][:address][:address_line1] = address[:address1] unless address[:address1].blank?
+ post[:shipping][:address][:address_line2] = address[:address2] unless address[:address2].blank?
+ post[:shipping][:address][:city] = address[:city] unless address[:city].blank?
+ post[:shipping][:address][:state] = address[:state] unless address[:state].blank?
+ post[:shipping][:address][:country] = address[:country] unless address[:country].blank?
+ post[:shipping][:address][:zip] = address[:zip] unless address[:zip].blank?
+ end
+ end
+
def add_transaction_data(post, options = {})
post[:payment_type] = 'Regular' if options[:transaction_indicator] == 1
post[:payment_type] = 'Recurring' if options[:transaction_indicator] == 2
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 85ee613e717..fbc1fddeb78 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -10,6 +10,7 @@ def setup
@amount = 200
@credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: Time.now.year + 1)
+ @credit_card_dnh = credit_card('4024007181869214', verification_value: '100', month: '6', year: Time.now.year + 1)
@expired_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2010')
@declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: Time.now.year + 1)
@threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: Time.now.year + 1)
@@ -65,6 +66,7 @@ def setup
@options = {
order_id: '1',
billing_address: address,
+ shipping_address: address,
description: 'Purchase',
email: 'longbob.longsen@example.com',
processing_channel_id: 'pc_lxgl7aqahkzubkundd2l546hdm'
@@ -73,10 +75,7 @@ def setup
card_on_file: true,
transaction_indicator: 2,
previous_charge_id: 'pay_123',
- processing_channel_id: 'pc_123',
- marketplace: {
- sub_entity_id: 'ent_123'
- }
+ processing_channel_id: 'pc_123'
)
@additional_options_3ds = @options.merge(
execute_threed: true,
@@ -313,8 +312,8 @@ def test_successful_purchase_includes_avs_result
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'S', response.avs_result['code']
- assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
+ assert_equal 'G', response.avs_result['code']
+ assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message']
end
def test_successful_purchase_includes_avs_result_via_oauth
@@ -329,8 +328,8 @@ def test_successful_authorize_includes_avs_result
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'S', response.avs_result['code']
- assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
+ assert_equal 'G', response.avs_result['code']
+ assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message']
end
def test_successful_purchase_includes_cvv_result
@@ -426,6 +425,12 @@ def test_successful_purchase_with_minimal_options
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_shipping_address
+ response = @gateway.purchase(@amount, @credit_card, shipping_address: address)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_purchase_without_phone_number
response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: ''))
assert_success response
@@ -439,7 +444,7 @@ def test_successful_purchase_with_ip
end
def test_failed_purchase
- response = @gateway.purchase(12305, @credit_card, @options)
+ response = @gateway.purchase(100, @credit_card_dnh, @options)
assert_failure response
assert_equal 'Declined - Do Not Honour', response.message
end
@@ -462,6 +467,12 @@ def test_avs_failed_authorize
assert_equal 'request_invalid: card_number_invalid', response.message
end
+ def test_invalid_shipping_address
+ response = @gateway.authorize(@amount, @credit_card, shipping_address: address.update(country: 'Canada'))
+ assert_failure response
+ assert_equal 'request_invalid: address_country_invalid', response.message
+ end
+
def test_successful_authorize_and_capture
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -558,9 +569,9 @@ def test_direct_3ds_authorize
end
def test_failed_authorize
- response = @gateway.authorize(12314, @credit_card, @options)
+ response = @gateway.authorize(12314, @declined_card, @options)
assert_failure response
- assert_equal 'Invalid Card Number', response.message
+ assert_equal 'request_invalid: card_number_invalid', response.message
end
def test_failed_authorize_via_oauth
@@ -816,8 +827,8 @@ def test_failed_verify
def test_expired_card_returns_error_code
response = @gateway.purchase(@amount, @expired_card, @options)
assert_failure response
- assert_equal 'request_invalid: card_expired', response.message
- assert_equal 'request_invalid: card_expired', response.error_code
+ assert_equal 'processing_error: card_expired', response.message
+ assert_equal 'processing_error: card_expired', response.error_code
end
def test_successful_purchase_with_idempotency_key
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index a1c04453366..ed0269243db 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -337,10 +337,7 @@ def test_successful_authorize_and_capture_with_additional_options
card_on_file: true,
transaction_indicator: 2,
previous_charge_id: 'pay_123',
- processing_channel_id: 'pc_123',
- marketplace: {
- sub_entity_id: 'ent_123'
- }
+ processing_channel_id: 'pc_123'
}
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
@@ -348,7 +345,6 @@ def test_successful_authorize_and_capture_with_additional_options
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"previous_payment_id":"pay_123"}, data)
assert_match(%r{"processing_channel_id":"pc_123"}, data)
- assert_match(/"marketplace\":{\"sub_entity_id\":\"ent_123\"}/, data)
end.respond_with(successful_authorize_response)
assert_success response
@@ -789,6 +785,26 @@ def test_supported_countries
assert_equal %w[AD AE AR AT AU BE BG BH BR CH CL CN CO CY CZ DE DK EE EG ES FI FR GB GR HK HR HU IE IS IT JO JP KW LI LT LU LV MC MT MX MY NL NO NZ OM PE PL PT QA RO SA SE SG SI SK SM TR US], @gateway.supported_countries
end
+ def test_add_shipping_address
+ options = {
+ shipping_address: address()
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['shipping']['address']['address_line1'], options[:shipping_address][:address1]
+ assert_equal request['shipping']['address']['address_line2'], options[:shipping_address][:address2]
+ assert_equal request['shipping']['address']['city'], options[:shipping_address][:city]
+ assert_equal request['shipping']['address']['state'], options[:shipping_address][:state]
+ assert_equal request['shipping']['address']['country'], options[:shipping_address][:country]
+ assert_equal request['shipping']['address']['zip'], options[:shipping_address][:zip]
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
private
def pre_scrubbed
From 88022e852daa2acf80aaf91265c70c437d12d56e Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 24 Apr 2023 10:49:48 -0400
Subject: [PATCH 058/390] Release 1.128.0
---
CHANGELOG | 2 ++
lib/active_merchant/version.rb | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index a77db13c6ef..a3111912106 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
* Element: Include Apple Pay - Google pay methods [jherrera] #4647
* dLocal: Add transaction query API(s) request [almalee24] #4584
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index a36602fe521..3d69284bab4 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.127.0'
+ VERSION = '1.128.0'
end
From d138569bbe1e1c18e21d0a5d9f89e2e0dda148a8 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Thu, 27 Apr 2023 16:31:06 -0400
Subject: [PATCH 059/390] Adyen: update selectedBrand mapping for Google Pay
Adyen advised that `googlepay` is the correct value to send for `selectedBrand`
CER-550
LOCAL
5498 tests, 77340 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
GATEWAY - UNIT TESTS
105 tests, 531 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
GATEWAY - REMOTE TESTS
132 tests, 443 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
90.9091% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index a3111912106..10a6a15f3c0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Adyen: update selectedBrand mapping for Google Pay [jcreiff] #4763
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 5b311b9c6fb..3936ca9da0e 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -223,7 +223,7 @@ def scrub(transcript)
NETWORK_TOKENIZATION_CARD_SOURCE = {
'apple_pay' => 'applepay',
'android_pay' => 'androidpay',
- 'google_pay' => 'paywithgoogle'
+ 'google_pay' => 'googlepay'
}
def add_extra_data(post, payment, options)
From a753cbc4521d6991d187d23b6560b4c86e4f8907 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Thu, 27 Apr 2023 16:04:46 -0400
Subject: [PATCH 060/390] Shift4: add vendorReference field
This change maps `options[:order_id]` to Shift4's `vendorReference` field
CER-563
LOCAL
5498 tests, 77341 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
GATEWAY UNIT TESTS
25 tests, 154 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
GATEWAY REMOTE TESTS
25 tests, 58 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
92% passed
(These failures also exist on the master branch)
---
CHANGELOG | 3 ++-
lib/active_merchant/billing/gateways/shift4.rb | 1 +
test/remote/gateways/remote_shift4_test.rb | 9 ++++++++-
test/unit/gateways/shift4_test.rb | 4 +++-
4 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 10a6a15f3c0..440bb442660 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,7 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
-* Adyen: update selectedBrand mapping for Google Pay [jcreiff] #4763
+* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
+* Shift4: Add vendorReference field [jcreiff] #4762
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 8e11769c121..3763a93adcc 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -185,6 +185,7 @@ def add_transaction(post, options)
post[:transaction] = {}
post[:transaction][:invoice] = options[:invoice] || Time.new.to_i.to_s[1..3] + rand.to_s[2..7]
post[:transaction][:notes] = options[:notes] if options[:notes].present?
+ post[:transaction][:vendorReference] = options[:order_id]
add_purchase_card(post[:transaction], options)
add_card_on_file(post[:transaction], options)
diff --git a/test/remote/gateways/remote_shift4_test.rb b/test/remote/gateways/remote_shift4_test.rb
index 5c13379c92f..b3289643b2b 100644
--- a/test/remote/gateways/remote_shift4_test.rb
+++ b/test/remote/gateways/remote_shift4_test.rb
@@ -18,7 +18,8 @@ def setup
tax: '2',
customer_reference: 'D019D09309F2',
destination_postal_code: '94719',
- product_descriptors: %w(Hamburger Fries Soda Cookie)
+ product_descriptors: %w(Hamburger Fries Soda Cookie),
+ order_id: '123456'
}
@customer_address = {
address1: '65 Easy St',
@@ -78,6 +79,12 @@ def test_successful_purchase_with_extra_options
assert_success response
end
+ def test_successful_purchase_passes_vendor_reference
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options))
+ assert_success response
+ assert_equal response_result(response)['transaction']['vendorReference'], @extra_options[:order_id]
+ end
+
def test_successful_purchase_with_stored_credential_framework
stored_credential_options = {
initial_transaction: true,
diff --git a/test/unit/gateways/shift4_test.rb b/test/unit/gateways/shift4_test.rb
index 0cddc546615..5f44b66fe10 100644
--- a/test/unit/gateways/shift4_test.rb
+++ b/test/unit/gateways/shift4_test.rb
@@ -13,7 +13,8 @@ def setup
tax: '2',
customer_reference: 'D019D09309F2',
destination_postal_code: '94719',
- product_descriptors: %w(Hamburger Fries Soda Cookie)
+ product_descriptors: %w(Hamburger Fries Soda Cookie),
+ order_id: '123456'
}
@customer_address = {
address1: '123 Street',
@@ -63,6 +64,7 @@ def test_successful_purchase_with_extra_fields
request = JSON.parse(data)
assert_equal request['clerk']['numericId'], @extra_options[:clerk_id]
assert_equal request['transaction']['notes'], @extra_options[:notes]
+ assert_equal request['transaction']['vendorReference'], @extra_options[:order_id]
assert_equal request['amount']['tax'], @extra_options[:tax].to_f
assert_equal request['amount']['total'], (@amount / 100.0).to_s
assert_equal request['transaction']['purchaseCard']['customerReference'], @extra_options[:customer_reference]
From a51d854d09b2bd70eeb7ceecc363b03c2ee41427 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 28 Apr 2023 13:13:33 -0500
Subject: [PATCH 061/390] Litle update the successful_from method
Add 001 and 010 to be considered successful responses for Litle.
Remote
57 tests, 251 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit
58 tests, 255 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/litle.rb | 15 +++++-
test/remote/gateways/remote_litle_test.rb | 54 +++++++++++--------
test/unit/gateways/litle_test.rb | 34 ++++++++++--
4 files changed, 77 insertions(+), 27 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 440bb442660..cde8e69d6c3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -100,6 +100,7 @@
* IPG: Improve error handling [heavyblade] #4753
* Shift4: Handle access token failed calls [heavyblade] #4745
* Bogus: Add verify functionality [willemk] #4749
+* Litle: Update successful_from method [almalee24] #4765
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb
index 2c5e55ebc06..ac5e4c66f5b 100644
--- a/lib/active_merchant/billing/gateways/litle.rb
+++ b/lib/active_merchant/billing/gateways/litle.rb
@@ -572,15 +572,26 @@ def commit(kind, request, money = nil)
cvv_result: parsed[:fraudResult_cardValidationResult]
}
- Response.new(success_from(kind, parsed), parsed[:message], parsed, options)
+ Response.new(success_from(kind, parsed), message_from(parsed), parsed, options)
end
def success_from(kind, parsed)
- return (parsed[:response] == '000') unless kind == :registerToken
+ return %w(000 001 010).any?(parsed[:response]) unless kind == :registerToken
%w(000 801 802).include?(parsed[:response])
end
+ def message_from(parsed)
+ case parsed[:response]
+ when '010'
+ return "#{parsed[:message]}: The authorized amount is less than the requested amount."
+ when '001'
+ return "#{parsed[:message]}: This is sent to acknowledge that the submitted transaction has been received."
+ else
+ parsed[:message]
+ end
+ end
+
def authorization_from(kind, parsed, money)
kind == :registerToken ? parsed[:litleToken] : "#{parsed[:litleTxnId]};#{kind};#{money}"
end
diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb
index 465f9b1cc4b..4cff45413cf 100644
--- a/test/remote/gateways/remote_litle_test.rb
+++ b/test/remote/gateways/remote_litle_test.rb
@@ -92,6 +92,8 @@ def setup
routing_number: '011100012',
account_number: '1099999998'
)
+
+ @declined_card = credit_card('4488282659650110', first_name: nil, last_name: 'REFUSED')
end
def test_successful_authorization
@@ -143,14 +145,22 @@ def test_successful_authorization_with_echeck
assert_equal 'Approved', response.message
end
- def test_avs_and_cvv_result
+ def test_avs_result
+ @credit_card1.number = '4200410886320101'
assert response = @gateway.authorize(10010, @credit_card1, @options)
- assert_equal 'X', response.avs_result['code']
- assert_equal 'M', response.cvv_result['code']
+
+ assert_equal 'Z', response.avs_result['code']
+ end
+
+ def test__cvv_result
+ @credit_card1.number = '4100521234567000'
+ assert response = @gateway.authorize(10010, @credit_card1, @options)
+
+ assert_equal 'P', response.cvv_result['code']
end
def test_unsuccessful_authorization
- assert response = @gateway.authorize(60060, @credit_card2,
+ assert response = @gateway.authorize(60060, @declined_card,
{
order_id: '6',
billing_address: {
@@ -231,7 +241,7 @@ def test_successful_purchase_with_3ds_fields
def test_successful_purchase_with_apple_pay
assert response = @gateway.purchase(10010, @decrypted_apple_pay)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_successful_purchase_with_android_pay
@@ -370,7 +380,7 @@ def test_successful_purchase_with_echeck
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(60060, @credit_card2, {
+ assert response = @gateway.purchase(60060, @declined_card, {
order_id: '6',
billing_address: {
name: 'Joe Green',
@@ -398,6 +408,8 @@ def test_authorize_capture_refund_void
assert_success refund
assert_equal 'Approved', refund.message
+ sleep 40.seconds
+
assert void = @gateway.void(refund.authorization)
assert_success void
assert_equal 'Approved', void.message
@@ -422,7 +434,7 @@ def test_authorize_and_capture_with_stored_credential_recurring
)
assert auth = @gateway.authorize(4999, credit_card, initial_options)
assert_success auth
- assert_equal 'Approved', auth.message
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', auth.message
assert network_transaction_id = auth.params['networkTransactionId']
assert capture = @gateway.capture(4999, auth.authorization)
@@ -440,7 +452,7 @@ def test_authorize_and_capture_with_stored_credential_recurring
)
assert auth = @gateway.authorize(4999, credit_card, used_options)
assert_success auth
- assert_equal 'Approved', auth.message
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', auth.message
assert capture = @gateway.capture(4999, auth.authorization)
assert_success capture
@@ -642,9 +654,9 @@ def test_void_authorization
end
def test_unsuccessful_void
- assert void = @gateway.void('123456789012345360;authorization;100')
+ assert void = @gateway.void('1234567890r2345360;authorization;100')
assert_failure void
- assert_equal 'No transaction found with specified Transaction Id', void.message
+ assert_match(/^Error validating xml data against the schema/, void.message)
end
def test_successful_credit
@@ -710,15 +722,15 @@ def test_nil_amount_capture
end
def test_capture_unsuccessful
- assert capture_response = @gateway.capture(10010, '123456789012345360')
+ assert capture_response = @gateway.capture(10010, '123456789w123')
assert_failure capture_response
- assert_equal 'No transaction found with specified Transaction Id', capture_response.message
+ assert_match(/^Error validating xml data against the schema/, capture_response.message)
end
def test_refund_unsuccessful
- assert credit_response = @gateway.refund(10010, '123456789012345360')
+ assert credit_response = @gateway.refund(10010, '123456789w123')
assert_failure credit_response
- assert_equal 'No transaction found with specified Transaction Id', credit_response.message
+ assert_match(/^Error validating xml data against the schema/, credit_response.message)
end
def test_void_unsuccessful
@@ -733,10 +745,8 @@ def test_store_successful
assert_success store_response
assert_equal 'Account number was successfully registered', store_response.message
- assert_equal '445711', store_response.params['bin']
- assert_equal 'VI', store_response.params['type']
assert_equal '801', store_response.params['response']
- assert_equal '1111222233330123', store_response.params['litleToken']
+ assert_equal '1111222233334444', store_response.params['litleToken']
end
def test_store_with_paypage_registration_id_successful
@@ -750,11 +760,11 @@ def test_store_with_paypage_registration_id_successful
end
def test_store_unsuccessful
- credit_card = CreditCard.new(@credit_card_hash.merge(number: '4457119999999999'))
+ credit_card = CreditCard.new(@credit_card_hash.merge(number: '4100282090123000'))
assert store_response = @gateway.store(credit_card, order_id: '51')
assert_failure store_response
- assert_equal 'Credit card number was invalid', store_response.message
+ assert_equal 'Credit card Number was invalid', store_response.message
assert_equal '820', store_response.params['response']
end
@@ -768,7 +778,7 @@ def test_store_and_purchase_with_token_successful
assert response = @gateway.purchase(10010, token)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_purchase_with_token_and_date_successful
@@ -780,7 +790,7 @@ def test_purchase_with_token_and_date_successful
assert response = @gateway.purchase(10010, token, { basis_expiration_month: '01', basis_expiration_year: '2024' })
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_echeck_store_and_purchase
@@ -793,7 +803,7 @@ def test_echeck_store_and_purchase
assert response = @gateway.purchase(10010, token)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_successful_verify
diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb
index 88b81d9d23c..4c2314894aa 100644
--- a/test/unit/gateways/litle_test.rb
+++ b/test/unit/gateways/litle_test.rb
@@ -76,7 +76,35 @@ def test_successful_purchase
end.respond_with(successful_purchase_response)
assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal '100000000000000006;sale;100', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_010_response
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, _data, _headers|
+ # Counterpoint to test_successful_postlive_url:
+ assert_match(/www\.testvantivcnp\.com/, endpoint)
+ end.respond_with(successful_purchase_response('010', 'Partially Approved'))
+
+ assert_success response
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
+ assert_equal '100000000000000006;sale;100', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_001_response
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, _data, _headers|
+ # Counterpoint to test_successful_postlive_url:
+ assert_match(/www\.testvantivcnp\.com/, endpoint)
+ end.respond_with(successful_purchase_response('001', 'Transaction Received'))
+ assert_success response
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', response.message
assert_equal '100000000000000006;sale;100', response.authorization
assert response.test?
end
@@ -732,15 +760,15 @@ def network_transaction_id
'63225578415568556365452427825'
end
- def successful_purchase_response
+ def successful_purchase_response(code = '000', message = 'Approved')
%(
100000000000000006
1
- 000
+ #{code}
2014-03-31T11:34:39
- Approved
+ #{message}
11111
01
From 3a847140db1fa0e6c8ba49fea4aa12be71105fc1 Mon Sep 17 00:00:00 2001
From: aenand
Date: Fri, 21 Apr 2023 11:14:45 -0400
Subject: [PATCH 062/390] Improve error handling: OAuth
ECS-2845
OAuth has become a standard authentication
mechanism for many gateways in recent years
however AM has not been updated to support error
handling when a merchant passes incorrect details
to the gateway.
In non OAuth flows we would return a failed response.
This commit now raises a new exception type indicating that
the request failed at the OAuth stage rather than the
transaction stage of a request
Remote:
25 tests, 57 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
92% passed
---
CHANGELOG | 1 +
.../billing/gateways/shift4.rb | 2 +-
lib/active_merchant/errors.rb | 5 ++++-
test/remote/gateways/remote_shift4_test.rb | 7 ++++++
test/unit/gateways/shift4_test.rb | 22 ++++++++++++++++++-
5 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index cde8e69d6c3..77d8652a8ca 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
* Shift4: Add vendorReference field [jcreiff] #4762
+* Shift4: Add OAuth error [aenand] #4760
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 3763a93adcc..6877f8e499a 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -153,7 +153,7 @@ def setup_access_token
add_datetime(post, options)
response = commit('accesstoken', post, request_headers('accesstoken', options))
- raise ArgumentError, response.params.fetch('result', [{}]).first.dig('error', 'longText') unless response.success?
+ raise OAuthResponseError.new(response, response.params.fetch('result', [{}]).first.dig('error', 'longText')) unless response.success?
response.params['result'].first['credential']['accessToken']
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index af4bcb8b1be..b017c45114a 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,10 +23,13 @@ def initialize(response, message = nil)
end
def to_s
- "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
+ "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
+ class OAuthResponseError < ResponseError # :nodoc:
+ end
+
class ClientCertificateError < ActiveMerchantError # :nodoc
end
diff --git a/test/remote/gateways/remote_shift4_test.rb b/test/remote/gateways/remote_shift4_test.rb
index b3289643b2b..0b4f3a0ef52 100644
--- a/test/remote/gateways/remote_shift4_test.rb
+++ b/test/remote/gateways/remote_shift4_test.rb
@@ -243,6 +243,13 @@ def test_failed_void
assert_include response.message, 'Invoice Not Found'
end
+ def test_failed_access_token
+ gateway = Shift4Gateway.new({ client_guid: 'YOUR_CLIENT_ID', auth_token: 'YOUR_AUTH_TOKEN' })
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.setup_access_token
+ end
+ end
+
private
def response_result(response)
diff --git a/test/unit/gateways/shift4_test.rb b/test/unit/gateways/shift4_test.rb
index 5f44b66fe10..bf187782c3e 100644
--- a/test/unit/gateways/shift4_test.rb
+++ b/test/unit/gateways/shift4_test.rb
@@ -360,9 +360,11 @@ def test_scrub
def test_setup_access_token_should_rise_an_exception_under_unsuccessful_request
@gateway.expects(:ssl_post).returns(failed_auth_response)
- assert_raises(ArgumentError) do
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
@gateway.setup_access_token
end
+
+ assert_match(/Failed with AuthToken not valid ENGINE22CE/, error.message)
end
def test_setup_access_token_should_successfully_extract_the_token_from_response
@@ -1024,6 +1026,24 @@ def failed_auth_response
RESPONSE
end
+ def failed_auth_response_no_message
+ <<-RESPONSE
+ {
+ "result": [
+ {
+ "error": {
+ "secondaryCode": 4,
+ "shortText ": "AuthToken"
+ },
+ "server": {
+ "name": "UTGAPI03CE"
+ }
+ }
+ ]
+ }
+ RESPONSE
+ end
+
def sucess_auth_response
<<-RESPONSE
{
From 65d15218c337c0b91379bfbdf9102701c48f4617 Mon Sep 17 00:00:00 2001
From: Britney Smith
Date: Tue, 25 Apr 2023 14:38:41 -0400
Subject: [PATCH 063/390] Stripe PI: Add billing address when tokenizing for
ApplePay and GooglePay
This adds functionality to add the card's billing address to digital wallets ApplePay and GooglePay. The billing address is available on the result of the card tokenization and is saved to the created PaymentIntent.
The remote test failures also exist on `master`.
Test Summary
Local: 5500 tests, 77348 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit: 83 tests, 368 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
93.9759% passed
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 12 ++++++
.../remote_stripe_payment_intents_test.rb | 41 +++++++++++++++++++
.../gateways/stripe_payment_intents_test.rb | 41 +++++++++++++++++++
4 files changed, 95 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 77d8652a8ca..429c550ccf2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
* Shift4: Add vendorReference field [jcreiff] #4762
* Shift4: Add OAuth error [aenand] #4760
+* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 832908430a5..008726c08e8 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -358,6 +358,7 @@ def tokenize_apple_google(payment, options = {})
cryptogram: payment.payment_cryptogram
}
}
+ add_billing_address_for_card_tokenization(post, options) if %i(apple_pay android_pay).include?(tokenization_method)
token_response = api_request(:post, 'tokens', post, options)
success = token_response['error'].nil?
if success && token_response['id']
@@ -474,6 +475,17 @@ def setup_future_usage(post, options = {})
post
end
+ def add_billing_address_for_card_tokenization(post, options = {})
+ return unless (billing = options[:billing_address] || options[:address])
+
+ post[:card][:address_city] = billing[:city] if billing[:city]
+ post[:card][:address_country] = billing[:country] if billing[:country]
+ post[:card][:address_line1] = billing[:address1] if billing[:address1]
+ post[:card][:address_line2] = billing[:address2] if billing[:address2]
+ post[:card][:address_zip] = billing[:zip] if billing[:zip]
+ post[:card][:address_state] = billing[:state] if billing[:state]
+ end
+
def add_billing_address(post, options = {})
return unless billing = options[:billing_address] || options[:address]
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 346af9f4f67..b5c4a98d7ab 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -184,6 +184,33 @@ def test_successful_authorize_with_google_pay
assert_match('google_pay', auth.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
end
+ def test_successful_purchase_with_google_pay
+ options = {
+ currency: 'GBP'
+ }
+
+ purchase = @gateway.purchase(@amount, @google_pay, options)
+
+ assert_match('android_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ assert purchase.success?
+ assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ end
+
+ def test_successful_purchase_with_google_pay_when_sending_the_billing_address
+ options = {
+ currency: 'GBP',
+ billing_address: address
+ }
+
+ purchase = @gateway.purchase(@amount, @google_pay, options)
+
+ assert_match('android_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ billing_address_line1 = purchase.responses.first.params.dig('token', 'card', 'address_line1')
+ assert_equal '456 My Street', billing_address_line1
+ assert purchase.success?
+ assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ end
+
def test_successful_purchase_with_apple_pay
options = {
currency: 'GBP'
@@ -195,6 +222,20 @@ def test_successful_purchase_with_apple_pay
assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
end
+ def test_successful_purchase_with_apple_pay_when_sending_the_billing_address
+ options = {
+ currency: 'GBP',
+ billing_address: address
+ }
+
+ purchase = @gateway.purchase(@amount, @apple_pay, options)
+ assert_match('apple_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ billing_address_line1 = purchase.responses.first.params.dig('token', 'card', 'address_line1')
+ assert_equal '456 My Street', billing_address_line1
+ assert purchase.success?
+ assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ end
+
def test_succesful_purchase_with_connect_for_apple_pay
options = {
stripe_account: @destination_account
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index d4d1c8d799b..683642b0628 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -403,6 +403,21 @@ def test_purchase_with_google_pay
end.respond_with(successful_create_intent_response)
end
+ def test_purchase_with_google_pay_with_billing_address
+ options = {
+ currency: 'GBP',
+ billing_address: address
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @google_pay, options)
+ end.check_request do |_method, endpoint, data, _headers|
+ assert_match('card[tokenization_method]=android_pay', data) if %r{/tokens}.match?(endpoint)
+ assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint)
+ assert_match('wallet[type]=google_pay', data) if %r{/wallet}.match?(endpoint)
+ assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint)
+ end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address)
+ end
+
def test_purchase_with_shipping_options
options = {
currency: 'GBP',
@@ -465,6 +480,20 @@ def test_authorize_with_apple_pay
end.respond_with(successful_create_intent_response)
end
+ def test_authorize_with_apple_pay_with_billing_address
+ options = {
+ currency: 'GBP',
+ billing_address: address
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @apple_pay, options)
+ end.check_request do |_method, endpoint, data, _headers|
+ assert_match('card[tokenization_method]=apple_pay', data) if %r{/tokens}.match?(endpoint)
+ assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint)
+ assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint)
+ end.respond_with(successful_create_intent_response_with_apple_pay_and_billing_address)
+ end
+
def test_stored_credentials_does_not_override_ntid_field
network_transaction_id = '1098510912210968'
sc_network_transaction_id = '1078784111114777'
@@ -695,6 +724,18 @@ def successful_create_intent_response
RESPONSE
end
+ def successful_create_intent_response_with_apple_pay_and_billing_address
+ <<-RESPONSE
+ {"id"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0mqdAWOtgoysog1EpiFDCD", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>15, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"apple_pay"=>{"type"=>"apple_pay"}, "dynamic_last4"=>"4242", "type"=>"apple_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKPTGn6IGMgZMGrHHLa46LBY0n2_9_Yar0wPTNukle4t28eKG0ZDZnxGYr6GyKn8VsKIEVjU4NkW8NHTL", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0mqdAWOtgoysog1HddFSKg/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0mqdAWOtgoysog1IQeiLiz"}, "client_secret"=>"pi_3N0mqdAWOtgoysog1IQeiLiz_secret_laDLUM6rVleLRqz0nMus9HktB", "confirmation_method"=>"automatic", "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}
+ RESPONSE
+ end
+
+ def successful_create_intent_response_with_google_pay_and_billing_address
+ <<-RESPONSE
+ {"id"=>"pi_3N0nKLAWOtgoysog3cRTGUqD", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0nKLAWOtgoysog3npJdWNI", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0nKLAWOtgoysog3ZAmtAMT", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682434726, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>61, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0nKLAWOtgoysog3cRTGUqD", "payment_method"=>"pm_1N0nKLAWOtgoysoglKSvcZz9", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"dynamic_last4"=>"4242", "google_pay"=>{}, "type"=>"google_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKbVn6IGMgbEjx6eavI6LBZciyBuj3wwsvIi6Fdr1gNyM807fxUBTGDg2j_1c42EB8vLZl4KcSJA0otk", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0nKLAWOtgoysog3npJdWNI/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0nKLAWOtgoysog3cRTGUqD"}, "client_secret"=>"pi_3N0nKLAWOtgoysog3cRTGUqD_secret_L4UFErMf6H4itOcZrZRqTwsuA", "confirmation_method"=>"automatic", "created"=>1682434725, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0nKLAWOtgoysog3npJdWNI", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0nKLAWOtgoysoglKSvcZz9", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}
+ RESPONSE
+ end
+
def successful_capture_response
<<-RESPONSE
{"id":"pi_1F1xauAWOtgoysogIfHO8jGi","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":2020,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1xavAWOtgoysogxrtSiCu4","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_1F1xawAWOtgoysog27xGBjM6","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":true,"created":1564501833,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":58,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1xauAWOtgoysogIfHO8jGi","payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1xavAWOtgoysogxrtSiCu4/rcpt_FX1eGdFRi8ssOY8Fqk4X6nEjNeGV5PG","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F1xavAWOtgoysogxrtSiCu4/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1xauAWOtgoysogIfHO8jGi"},"client_secret":"pi_1F1xauAWOtgoysogIfHO8jGi_secret_ZrXvfydFv0BelaMQJgHxjts5b","confirmation_method":"manual","created":1564501832,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}
From dad65a7cb70985e1b477231c4d7e33455d40e04c Mon Sep 17 00:00:00 2001
From: Espen Antonsen
Date: Tue, 2 May 2023 20:50:46 +0200
Subject: [PATCH 064/390] Rexml is no longer a default gem in Ruby 3 (#3852)
---
activemerchant.gemspec | 1 +
1 file changed, 1 insertion(+)
diff --git a/activemerchant.gemspec b/activemerchant.gemspec
index e72702e8afc..0fec7074593 100644
--- a/activemerchant.gemspec
+++ b/activemerchant.gemspec
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
s.add_dependency('i18n', '>= 0.6.9')
s.add_dependency('nokogiri', '~> 1.4')
+ s.add_dependency('rexml')
s.add_development_dependency('mocha', '~> 1')
s.add_development_dependency('pry')
From cd91ddec32a4fa3c5dcfbb08b1210dcc15d81309 Mon Sep 17 00:00:00 2001
From: Pierre Nespo
Date: Tue, 2 May 2023 14:55:19 -0400
Subject: [PATCH 065/390] Revert "Rexml is no longer a default gem in Ruby 3
(#3852)" (#4767)
This reverts commit dad65a7cb70985e1b477231c4d7e33455d40e04c. It caused ci to fail due to a rexml issue.
---
activemerchant.gemspec | 1 -
1 file changed, 1 deletion(-)
diff --git a/activemerchant.gemspec b/activemerchant.gemspec
index 0fec7074593..e72702e8afc 100644
--- a/activemerchant.gemspec
+++ b/activemerchant.gemspec
@@ -26,7 +26,6 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
s.add_dependency('i18n', '>= 0.6.9')
s.add_dependency('nokogiri', '~> 1.4')
- s.add_dependency('rexml')
s.add_development_dependency('mocha', '~> 1')
s.add_development_dependency('pry')
From eed45fe28a355626dfb0f560ede5cf719da4e9f9 Mon Sep 17 00:00:00 2001
From: Pierre Nespo
Date: Wed, 3 May 2023 12:50:59 -0400
Subject: [PATCH 066/390] Add rexml as a gem dependency (#4768)
* Add rexml as a gem dependency
Rexml is no longer included with Ruby 3+, we therefore need to add the dependency explicitely.
* Remove garbage character from test file
---
activemerchant.gemspec | 1 +
test/unit/gateways/payflow_test.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/activemerchant.gemspec b/activemerchant.gemspec
index e72702e8afc..3aed581dc47 100644
--- a/activemerchant.gemspec
+++ b/activemerchant.gemspec
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
s.add_dependency('i18n', '>= 0.6.9')
s.add_dependency('nokogiri', '~> 1.4')
+ s.add_dependency('rexml', '~> 3.2.5')
s.add_development_dependency('mocha', '~> 1')
s.add_development_dependency('pry')
diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb
index c87aedd255a..3d0a4cdb5fb 100644
--- a/test/unit/gateways/payflow_test.rb
+++ b/test/unit/gateways/payflow_test.rb
@@ -1144,7 +1144,7 @@ def xpath_prefix_for_transaction_type(tx_type)
def threeds_xpath_for_extdata(attr_name, tx_type: 'Authorization')
xpath_prefix = xpath_prefix_for_transaction_type(tx_type)
- %(string(#{xpath_prefix}/PayData/ExtData[@Name='#{attr_name}']/@Value)")
+ %(string(#{xpath_prefix}/PayData/ExtData[@Name='#{attr_name}']/@Value))
end
def authorize_buyer_auth_result_path
From 7c82f821eebdcd5c2ef9328accae2d7e35a410c4 Mon Sep 17 00:00:00 2001
From: Pierre Nespo
Date: Wed, 3 May 2023 13:16:53 -0400
Subject: [PATCH 067/390] Release v1.129.0
---
CHANGELOG | 3 +++
lib/active_merchant/version.rb | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 429c550ccf2..e3d1ddb0c92 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,10 +2,13 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.129.0 (April 24th, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
* Shift4: Add vendorReference field [jcreiff] #4762
* Shift4: Add OAuth error [aenand] #4760
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
+* Make gem compatible with Ruby 3+ [pi3r] #4768
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index 3d69284bab4..c51365b650a 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.128.0'
+ VERSION = '1.129.0'
end
From 6e3cd4b431fdd521e955d80fa3133f6dc2653899 Mon Sep 17 00:00:00 2001
From: Alejandro Flores
Date: Mon, 26 Dec 2022 10:25:38 -0600
Subject: [PATCH 068/390] Mit: Changed how the payload was sent to the gateway
Closes #4655
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/mit.rb | 27 ++++++---------------
test/unit/gateways/mit_test.rb | 12 ++++++---
3 files changed, 16 insertions(+), 24 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index e3d1ddb0c92..5b207ad5d3f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
* Shift4: Add OAuth error [aenand] #4760
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
* Make gem compatible with Ruby 3+ [pi3r] #4768
+* Mit: Update how payload is delivered to the gateway [alejandrofloresm] #4655
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/mit.rb b/lib/active_merchant/billing/gateways/mit.rb
index 74b7bf6beab..ee33d43c9a0 100644
--- a/lib/active_merchant/billing/gateways/mit.rb
+++ b/lib/active_merchant/billing/gateways/mit.rb
@@ -93,8 +93,7 @@ def authorize(money, payment, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('sale', json_post)
end
@@ -114,8 +113,7 @@ def capture(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('capture', json_post)
end
@@ -136,8 +134,7 @@ def refund(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('refund', json_post)
end
@@ -149,6 +146,7 @@ def scrub(transcript)
ret_transcript = transcript
auth_origin = ret_transcript[/(.*?)<\/authorization>/, 1]
unless auth_origin.nil?
+ auth_origin = auth_origin.gsub('\n', '')
auth_decrypted = decrypt(auth_origin, @options[:key_session])
auth_json = JSON.parse(auth_decrypted)
auth_json['card'] = '[FILTERED]'
@@ -162,6 +160,7 @@ def scrub(transcript)
cap_origin = ret_transcript[/(.*?)<\/capture>/, 1]
unless cap_origin.nil?
+ cap_origin = cap_origin.gsub('\n', '')
cap_decrypted = decrypt(cap_origin, @options[:key_session])
cap_json = JSON.parse(cap_decrypted)
cap_json['apikey'] = '[FILTERED]'
@@ -173,6 +172,7 @@ def scrub(transcript)
ref_origin = ret_transcript[/(.*?)<\/refund>/, 1]
unless ref_origin.nil?
+ ref_origin = ref_origin.gsub('\n', '')
ref_decrypted = decrypt(ref_origin, @options[:key_session])
ref_json = JSON.parse(ref_decrypted)
ref_json['apikey'] = '[FILTERED]'
@@ -182,17 +182,6 @@ def scrub(transcript)
ret_transcript = ret_transcript.gsub(/(.*?)<\/refund>/, ref_tagged)
end
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
- loop do
- break if res_origin.nil?
-
- resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
- resp_decrypted = decrypt(resp_origin, @options[:key_session])
- ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
- ret_transcript = ret_transcript.sub('reading ', 'response: ')
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
- end
-
ret_transcript
end
@@ -219,9 +208,7 @@ def add_payment(post, payment)
end
def commit(action, parameters)
- json_str = JSON.generate(parameters)
- cleaned_str = json_str.gsub('\n', '')
- raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
+ raw_response = ssl_post(live_url, parameters, { 'Content-type' => 'text/plain' })
response = JSON.parse(decrypt(raw_response, @options[:key_session]))
Response.new(
diff --git a/test/unit/gateways/mit_test.rb b/test/unit/gateways/mit_test.rb
index 16a80355f4f..64210d0fb0e 100644
--- a/test/unit/gateways/mit_test.rb
+++ b/test/unit/gateways/mit_test.rb
@@ -218,7 +218,7 @@ def post_scrubbed
starting SSL for wpy.mitec.com.mx:443...
SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
<- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Authorize","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","amount":"11.15","currency":"MXN","reference":"721","transaction_id":"721","installments":1,"card":"[FILTERED]","expmonth":9,"expyear":2025,"cvv":"[FILTERED]","name_client":"Pedro Flores Valdes","email":"nadie@mit.test","key_session":"[FILTERED]"}IVCA33721\"}"
+ <- "{\"payload\":\"{\"operation\":\"Authorize\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"amount\":\"11.15\",\"currency\":\"MXN\",\"reference\":\"721\",\"transaction_id\":\"721\",\"installments\":1,\"card\":\"[FILTERED]\",\"expmonth\":9,\"expyear\":2025,\"cvv\":\"[FILTERED]\",\"name_client\":\"Pedro Flores Valdes\",\"email\":\"nadie@mit.test\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
-> "HTTP/1.1 200 \r\n"
-> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
-> "X-Content-Type-Options: nosniff\r\n"
@@ -230,14 +230,16 @@ def post_scrubbed
-> "Server: \r\n"
-> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
-> "\r\n"
- response: {"folio_cdp":"095492846","auth":"928468","response":"approved","message":"0C- Pago aprobado (test)","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:08 06:09:2021","operation":"Authorize"}read 320 bytes
+ reading 320 bytes...
+ -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
+ read 320 bytes
Conn close
opening connection to wpy.mitec.com.mx:443...
opened
starting SSL for wpy.mitec.com.mx:443...
SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
<- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Capture","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","transaction_id":"721","amount":"11.15","key_session":"[FILTERED]"}IVCA33721\"}"
+ <- "{\"payload\":\"{\"operation\":\"Capture\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"transaction_id\":\"721\",\"amount\":\"11.15\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
-> "HTTP/1.1 200 \r\n"
-> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
-> "X-Content-Type-Options: nosniff\r\n"
@@ -249,7 +251,9 @@ def post_scrubbed
-> "Server: \r\n"
-> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
-> "\r\n"
- response: {"folio_cdp":"095492915","auth":"929151","response":"approved","message":"0C- ","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:09 06:09:2021","operation":"Capture"}read 280 bytes
+ reading 280 bytes...
+ -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
+ read 280 bytes
Conn close
POST_SCRUBBED
end
From 00f1189edab256b7a7d898368c0eeece2ddcbb1b Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Wed, 3 May 2023 15:12:03 -0400
Subject: [PATCH 069/390] Revert "Mit: Changed how the payload was sent to the
gateway"
This reverts commit 6e3cd4b431fdd521e955d80fa3133f6dc2653899.
---
CHANGELOG | 1 -
lib/active_merchant/billing/gateways/mit.rb | 27 +++++++++++++++------
test/unit/gateways/mit_test.rb | 12 +++------
3 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5b207ad5d3f..e3d1ddb0c92 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,7 +9,6 @@
* Shift4: Add OAuth error [aenand] #4760
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
* Make gem compatible with Ruby 3+ [pi3r] #4768
-* Mit: Update how payload is delivered to the gateway [alejandrofloresm] #4655
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/mit.rb b/lib/active_merchant/billing/gateways/mit.rb
index ee33d43c9a0..74b7bf6beab 100644
--- a/lib/active_merchant/billing/gateways/mit.rb
+++ b/lib/active_merchant/billing/gateways/mit.rb
@@ -93,7 +93,8 @@ def authorize(money, payment, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = final_post
+ json_post = {}
+ json_post[:payload] = final_post
commit('sale', json_post)
end
@@ -113,7 +114,8 @@ def capture(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = final_post
+ json_post = {}
+ json_post[:payload] = final_post
commit('capture', json_post)
end
@@ -134,7 +136,8 @@ def refund(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = final_post
+ json_post = {}
+ json_post[:payload] = final_post
commit('refund', json_post)
end
@@ -146,7 +149,6 @@ def scrub(transcript)
ret_transcript = transcript
auth_origin = ret_transcript[/(.*?)<\/authorization>/, 1]
unless auth_origin.nil?
- auth_origin = auth_origin.gsub('\n', '')
auth_decrypted = decrypt(auth_origin, @options[:key_session])
auth_json = JSON.parse(auth_decrypted)
auth_json['card'] = '[FILTERED]'
@@ -160,7 +162,6 @@ def scrub(transcript)
cap_origin = ret_transcript[/(.*?)<\/capture>/, 1]
unless cap_origin.nil?
- cap_origin = cap_origin.gsub('\n', '')
cap_decrypted = decrypt(cap_origin, @options[:key_session])
cap_json = JSON.parse(cap_decrypted)
cap_json['apikey'] = '[FILTERED]'
@@ -172,7 +173,6 @@ def scrub(transcript)
ref_origin = ret_transcript[/(.*?)<\/refund>/, 1]
unless ref_origin.nil?
- ref_origin = ref_origin.gsub('\n', '')
ref_decrypted = decrypt(ref_origin, @options[:key_session])
ref_json = JSON.parse(ref_decrypted)
ref_json['apikey'] = '[FILTERED]'
@@ -182,6 +182,17 @@ def scrub(transcript)
ret_transcript = ret_transcript.gsub(/(.*?)<\/refund>/, ref_tagged)
end
+ res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
+ loop do
+ break if res_origin.nil?
+
+ resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
+ resp_decrypted = decrypt(resp_origin, @options[:key_session])
+ ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
+ ret_transcript = ret_transcript.sub('reading ', 'response: ')
+ res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
+ end
+
ret_transcript
end
@@ -208,7 +219,9 @@ def add_payment(post, payment)
end
def commit(action, parameters)
- raw_response = ssl_post(live_url, parameters, { 'Content-type' => 'text/plain' })
+ json_str = JSON.generate(parameters)
+ cleaned_str = json_str.gsub('\n', '')
+ raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
response = JSON.parse(decrypt(raw_response, @options[:key_session]))
Response.new(
diff --git a/test/unit/gateways/mit_test.rb b/test/unit/gateways/mit_test.rb
index 64210d0fb0e..16a80355f4f 100644
--- a/test/unit/gateways/mit_test.rb
+++ b/test/unit/gateways/mit_test.rb
@@ -218,7 +218,7 @@ def post_scrubbed
starting SSL for wpy.mitec.com.mx:443...
SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
<- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"{\"operation\":\"Authorize\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"amount\":\"11.15\",\"currency\":\"MXN\",\"reference\":\"721\",\"transaction_id\":\"721\",\"installments\":1,\"card\":\"[FILTERED]\",\"expmonth\":9,\"expyear\":2025,\"cvv\":\"[FILTERED]\",\"name_client\":\"Pedro Flores Valdes\",\"email\":\"nadie@mit.test\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ <- "{\"payload\":\"{"operation":"Authorize","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","amount":"11.15","currency":"MXN","reference":"721","transaction_id":"721","installments":1,"card":"[FILTERED]","expmonth":9,"expyear":2025,"cvv":"[FILTERED]","name_client":"Pedro Flores Valdes","email":"nadie@mit.test","key_session":"[FILTERED]"}IVCA33721\"}"
-> "HTTP/1.1 200 \r\n"
-> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
-> "X-Content-Type-Options: nosniff\r\n"
@@ -230,16 +230,14 @@ def post_scrubbed
-> "Server: \r\n"
-> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
-> "\r\n"
- reading 320 bytes...
- -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
- read 320 bytes
+ response: {"folio_cdp":"095492846","auth":"928468","response":"approved","message":"0C- Pago aprobado (test)","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:08 06:09:2021","operation":"Authorize"}read 320 bytes
Conn close
opening connection to wpy.mitec.com.mx:443...
opened
starting SSL for wpy.mitec.com.mx:443...
SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
<- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"{\"operation\":\"Capture\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"transaction_id\":\"721\",\"amount\":\"11.15\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ <- "{\"payload\":\"{"operation":"Capture","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","transaction_id":"721","amount":"11.15","key_session":"[FILTERED]"}IVCA33721\"}"
-> "HTTP/1.1 200 \r\n"
-> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
-> "X-Content-Type-Options: nosniff\r\n"
@@ -251,9 +249,7 @@ def post_scrubbed
-> "Server: \r\n"
-> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
-> "\r\n"
- reading 280 bytes...
- -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
- read 280 bytes
+ response: {"folio_cdp":"095492915","auth":"929151","response":"approved","message":"0C- ","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:09 06:09:2021","operation":"Capture"}read 280 bytes
Conn close
POST_SCRUBBED
end
From 41dcafd4323fe0ba767f0d0869b7cb449f3a3ec1 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Fri, 5 May 2023 14:22:21 -0700
Subject: [PATCH 070/390] PayuLatam: The original method of surfacing error
codes was redundant and didn't actually surface a network code that is
particularly useful. This PR aims to fix that issue for failing transactions.
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/payu_latam.rb | 2 +-
test/unit/gateways/payu_latam_test.rb | 4 +++-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index e3d1ddb0c92..ee01fb5a7c7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
* Shift4: Add OAuth error [aenand] #4760
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
* Make gem compatible with Ruby 3+ [pi3r] #4768
+* Payu Latam - Update error code method to surface network code [yunnydang] #4773
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/payu_latam.rb b/lib/active_merchant/billing/gateways/payu_latam.rb
index 89e52e494a9..3ac30eec018 100644
--- a/lib/active_merchant/billing/gateways/payu_latam.rb
+++ b/lib/active_merchant/billing/gateways/payu_latam.rb
@@ -455,7 +455,7 @@ def error_from(action, response)
when 'verify_credentials'
response['error'] || 'FAILED'
else
- response['transactionResponse']['errorCode'] || response['transactionResponse']['responseCode'] if response['transactionResponse']
+ response['transactionResponse']['paymentNetworkResponseCode'] || response['transactionResponse']['errorCode'] if response['transactionResponse']
end
end
diff --git a/test/unit/gateways/payu_latam_test.rb b/test/unit/gateways/payu_latam_test.rb
index ed147aa7a23..664db4e5490 100644
--- a/test/unit/gateways/payu_latam_test.rb
+++ b/test/unit/gateways/payu_latam_test.rb
@@ -84,6 +84,7 @@ def test_failed_purchase_correct_message_when_payment_network_response_error_pre
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'CONTACT_THE_ENTITY | Contactar con entidad emisora', response.message
+ assert_equal '290', response.error_code
assert_equal 'Contactar con entidad emisora', response.params['transactionResponse']['paymentNetworkResponseErrorMessage']
@gateway.expects(:ssl_post).returns(failed_purchase_response_when_payment_network_response_error_not_expected)
@@ -91,6 +92,7 @@ def test_failed_purchase_correct_message_when_payment_network_response_error_pre
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'CONTACT_THE_ENTITY', response.message
+ assert_equal '51', response.error_code
assert_nil response.params['transactionResponse']['paymentNetworkResponseErrorMessage']
end
@@ -666,7 +668,7 @@ def failed_purchase_response_when_payment_network_response_error_not_expected
"orderId": 7354347,
"transactionId": "15b6cec0-9eec-4564-b6b9-c846b868203e",
"state": "DECLINED",
- "paymentNetworkResponseCode": null,
+ "paymentNetworkResponseCode": "51",
"paymentNetworkResponseErrorMessage": null,
"trazabilityCode": null,
"authorizationCode": null,
From 03f19596b924b9846c19c2ddf13fac6801d4e2e1 Mon Sep 17 00:00:00 2001
From: cristian
Date: Fri, 28 Apr 2023 11:54:43 -0500
Subject: [PATCH 071/390] CyberSource: Handling Canadian bank accounts
Summary:
------------------------------
Add changes to CyberSource to properly format canadian routing
numbers when payment methods is bank account.
GWS-48
Closes #4764
Remote Test:
------------------------------
Finished in 114.912426 seconds.
121 tests, 611 assertions, 5 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
95.8678% passed
*Notes on erros*:
The test suite was ran with a single account rason for errors:
- 2 errors correspondsto account not enabled for canadian bank transactions.
- 2 errors correspond to outdated 3ds PAR values.
- 1 error related with account not enabled for async.
Unit Tests:
------------------------------
Finished in 41.573188 seconds.
5501 tests, 77346 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 8 ++++++--
test/remote/gateways/remote_cyber_source_test.rb | 16 ++++++++++++++++
test/unit/gateways/cyber_source_test.rb | 12 ++++++++++++
4 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index ee01fb5a7c7..0edd7ca0fa1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
* Make gem compatible with Ruby 3+ [pi3r] #4768
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
+* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index bc11b577e3c..4549a6606d5 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -705,8 +705,8 @@ def add_mdd_fields(xml, options)
def add_check(xml, check, options)
xml.tag! 'check' do
xml.tag! 'accountNumber', check.account_number
- xml.tag! 'accountType', check.account_type[0]
- xml.tag! 'bankTransitNumber', check.routing_number
+ xml.tag! 'accountType', check.account_type == 'checking' ? 'C' : 'S'
+ xml.tag! 'bankTransitNumber', format_routing_number(check.routing_number, options)
xml.tag! 'secCode', options[:sec_code] if options[:sec_code]
end
end
@@ -1131,6 +1131,10 @@ def message_from(response)
def eligible_for_zero_auth?(payment_method, options = {})
payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
end
+
+ def format_routing_number(routing_number, options)
+ options[:currency] == 'CAD' && routing_number.length > 8 ? routing_number[-8..-1] : routing_number
+ end
end
end
end
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 2ab0c970f54..0ba41b55000 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -372,6 +372,22 @@ def test_successful_purchase_with_bank_account
assert_successful_response(response)
end
+ # To properly run this test couple of test your account needs to be enabled to
+ # handle canadian bank accounts.
+ def test_successful_purchase_with_a_canadian_bank_account_full_number
+ bank_account = check({ account_number: '4100', routing_number: '011000015' })
+ @options[:currency] = 'CAD'
+ assert response = @gateway.purchase(10000, bank_account, @options)
+ assert_successful_response(response)
+ end
+
+ def test_successful_purchase_with_a_canadian_bank_account_8_digit_number
+ bank_account = check({ account_number: '4100', routing_number: '11000015' })
+ @options[:currency] = 'CAD'
+ assert response = @gateway.purchase(10000, bank_account, @options)
+ assert_successful_response(response)
+ end
+
def test_successful_purchase_with_bank_account_savings_account
bank_account = check({ account_number: '4100', routing_number: '011000015', account_type: 'savings' })
assert response = @gateway.purchase(10000, bank_account, @options)
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 3e5c97e29b5..1223db12585 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -1462,6 +1462,18 @@ def test_raises_error_on_network_token_with_an_underlying_apms
assert_equal 'Payment method sodexo is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html', error.message
end
+ def test_routing_number_formatting_with_regular_routing_number
+ assert_equal @gateway.send(:format_routing_number, '012345678', { currency: 'USD' }), '012345678'
+ end
+
+ def test_routing_number_formatting_with_canadian_routing_number
+ assert_equal @gateway.send(:format_routing_number, '12345678', { currency: 'USD' }), '12345678'
+ end
+
+ def test_routing_number_formatting_with_canadian_routing_number_and_padding
+ assert_equal @gateway.send(:format_routing_number, '012345678', { currency: 'CAD' }), '12345678'
+ end
+
private
def options_with_normalized_3ds(
From 7d42e68e6ed3ec594641e677d0480cc5bb65b84c Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Tue, 9 May 2023 12:54:57 -0700
Subject: [PATCH 072/390] Update Changelog
---
CHANGELOG | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0edd7ca0fa1..7eebd5be60e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,15 +2,15 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Payu Latam - Update error code method to surface network code [yunnydang] #4773
+* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
-== Version 1.129.0 (April 24th, 2023)
+== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
* Shift4: Add vendorReference field [jcreiff] #4762
* Shift4: Add OAuth error [aenand] #4760
* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
* Make gem compatible with Ruby 3+ [pi3r] #4768
-* Payu Latam - Update error code method to surface network code [yunnydang] #4773
-* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
== Version 1.128.0 (April 24th, 2023)
* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
From 1aa48e48186667fee7805b6a98f130c39c56d982 Mon Sep 17 00:00:00 2001
From: cristian
Date: Fri, 12 May 2023 15:05:13 -0500
Subject: [PATCH 073/390] CyberSource Rest: Fixing currency detection
Summary:
------------------------------
Fix bug on Cybersource Rest to use other currencies
different than USD.
Closes #4777
Remote Test:
------------------------------
Finished in 46.080483 seconds.
43 tests, 141 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 33.72359 seconds.
5506 tests, 77384 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source_rest.rb | 8 ++++----
test/remote/gateways/remote_cyber_source_rest_test.rb | 10 ++++++++++
test/unit/gateways/cyber_source_rest_test.rb | 2 +-
4 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 7eebd5be60e..75546b37ab8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
+* CyberSource Rest: Fixing currency detection [heavyblade] #4777
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 7f6441cbeed..a82c1ed4f98 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -112,7 +112,7 @@ def build_auth_request(amount, payment, options)
add_code(post, options)
add_payment(post, payment, options)
add_mdd_fields(post, options)
- add_amount(post, amount)
+ add_amount(post, amount, options)
add_address(post, payment, options[:billing_address], options, :billTo)
add_address(post, payment, options[:shipping_address], options, :shipTo)
add_business_rules_data(post, payment, options)
@@ -125,7 +125,7 @@ def build_reference_request(amount, options)
{ clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
add_code(post, options)
add_mdd_fields(post, options)
- add_amount(post, amount)
+ add_amount(post, amount, options)
add_partner_solution_id(post)
end.compact
end
@@ -135,7 +135,7 @@ def build_credit_request(amount, payment, options)
add_code(post, options)
add_credit_card(post, payment)
add_mdd_fields(post, options)
- add_amount(post, amount)
+ add_amount(post, amount, options)
add_address(post, payment, options[:billing_address], options, :billTo)
add_merchant_description(post, options)
end.compact
@@ -161,7 +161,7 @@ def add_reversal_amount(post, amount)
}
end
- def add_amount(post, amount)
+ def add_amount(post, amount, options)
currency = options[:currency] || currency(amount)
post[:orderInformation][:amountDetails] = {
totalAmount: localized_amount(amount, currency),
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index 51ebe1a44e4..d39a9eb74ce 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -469,4 +469,14 @@ def test_successful_purchase_with_solution_id
ensure
ActiveMerchant::Billing::CyberSourceGateway.application_id = nil
end
+
+ def test_successful_purchase_in_australian_dollars
+ @options[:currency] = 'AUD'
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ assert_equal 'AUD', response.params['orderInformation']['amountDetails']['currency']
+ end
end
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index cbafb59e562..af92b99888b 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -125,7 +125,7 @@ def test_including_customer_if_customer_id_present
def test_add_ammount_and_currency
post = { orderInformation: {} }
- @gateway.send :add_amount, post, 10221
+ @gateway.send :add_amount, post, 10221, {}
assert_equal '102.21', post.dig(:orderInformation, :amountDetails, :totalAmount)
assert_equal 'USD', post.dig(:orderInformation, :amountDetails, :currency)
From f2585c178bfb36016740930f07b6bb90caa7a198 Mon Sep 17 00:00:00 2001
From: aenand
Date: Fri, 5 May 2023 11:34:52 -0400
Subject: [PATCH 074/390] Cybersource: Add business rules for NT
ECS-2849
A previous commit from 2015 restricted the ability to pass
business rules such as `ignoreAVSResult` and `ignoreCVResult` on
API requests with NetworkTokenization cards. Merchants are now
asking for this to be allowed on requests with payment methods
such as NT/AP/GP and the remote tests seem to indicate we can
add these fields for these types of payment methods.
Remote CBYS SOAP:
119 tests, 607 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.479% passed
Remote CYBS Rest:
46 tests, 152 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 8 ++--
.../billing/gateways/cyber_source_rest.rb | 6 +--
.../gateways/remote_cyber_source_rest_test.rb | 36 +++++++++++++++++
test/unit/gateways/cyber_source_test.rb | 40 +++++++++++++++++--
5 files changed, 79 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 75546b37ab8..9a0b249ed12 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
+* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 4549a6606d5..30c674e2f89 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -519,11 +519,9 @@ def build_retrieve_subscription_request(reference, options)
def add_business_rules_data(xml, payment_method, options)
prioritized_options = [options, @options]
- unless network_tokenization?(payment_method)
- xml.tag! 'businessRules' do
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
- end
+ xml.tag! 'businessRules' do
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
end
end
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index a82c1ed4f98..b4e58bdd635 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -413,10 +413,8 @@ def auth_headers(action, post, http_method = 'post')
def add_business_rules_data(post, payment, options)
post[:processingInformation][:authorizationOptions] = {}
- unless payment.is_a?(NetworkTokenizationCreditCard)
- post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
- post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
- end
+ post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
+ post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
end
def add_mdd_fields(post, options)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index d39a9eb74ce..c0405751bfc 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -165,6 +165,42 @@ def test_successful_purchase
assert_nil response.params['_links']['capture']
end
+ def test_successful_purchase_with_credit_card_ignore_avs
+ @options[:ignore_avs] = 'true'
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_network_token_ignore_avs
+ @options[:ignore_avs] = 'true'
+ response = @gateway.purchase(@amount, @apple_pay, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_credit_card_ignore_cvv
+ @options[:ignore_cvv] = 'true'
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_network_token_ignore_cvv
+ @options[:ignore_cvv] = 'true'
+ response = @gateway.purchase(@amount, @apple_pay, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
def test_successful_refund
purchase = @gateway.purchase(@amount, @visa_card, @options)
response = @gateway.refund(@amount, purchase.authorization, @options)
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 1223db12585..0dcb9dcc4dd 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -345,6 +345,23 @@ def test_successful_credit_cart_purchase_single_request_ignore_avs
assert_success response
end
+ def test_successful_network_token_purchase_single_request_ignore_avs
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_match %r'true', request_body
+ assert_not_match %r'', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram')
+ options = @options.merge(ignore_avs: true)
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ end
+
def test_successful_credit_cart_purchase_single_request_without_ignore_avs
@gateway.expects(:ssl_post).with do |_host, request_body|
assert_not_match %r'', request_body
@@ -383,6 +400,23 @@ def test_successful_credit_cart_purchase_single_request_ignore_ccv
assert_success response
end
+ def test_successful_network_token_purchase_single_request_ignore_cvv
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_match %r'true', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram')
+ options = @options.merge(ignore_cvv: true)
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ end
+
def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
@gateway.expects(:ssl_post).with do |_host, request_body|
assert_not_match %r'', request_body
@@ -824,7 +858,7 @@ def test_successful_auth_with_network_tokenization_for_visa
@gateway.authorize(@amount, credit_card, @options)
end.check_request do |_endpoint, body, _headers|
assert_xml_valid_to_xsd(body)
- assert_match %r'\n 111111111100cryptogram\n vbv\n 111111111100cryptogram\n\n\n 1\n', body
+ assert_match %r'\n 111111111100cryptogram\n vbv\n 111111111100cryptogram\n\n\n\n\n 1\n', body
end.respond_with(successful_purchase_response)
assert_success response
@@ -850,7 +884,7 @@ def test_successful_purchase_with_network_tokenization_for_visa
def test_successful_auth_with_network_tokenization_for_mastercard
@gateway.expects(:ssl_post).with do |_host, request_body|
assert_xml_valid_to_xsd(request_body)
- assert_match %r'\n 111111111100cryptogram\n 2\n\n\n spa\n\n\n 1\n', request_body
+ assert_match %r'\n 111111111100cryptogram\n 2\n\n\n spa\n\n\n\n\n 1\n', request_body
true
end.returns(successful_purchase_response)
@@ -867,7 +901,7 @@ def test_successful_auth_with_network_tokenization_for_mastercard
def test_successful_auth_with_network_tokenization_for_amex
@gateway.expects(:ssl_post).with do |_host, request_body|
assert_xml_valid_to_xsd(request_body)
- assert_match %r'\n MTExMTExMTExMTAwY3J5cHRvZ3I=\n\n aesk\n YW0=\n\n\n\n 1\n', request_body
+ assert_match %r'\n MTExMTExMTExMTAwY3J5cHRvZ3I=\n\n aesk\n YW0=\n\n\n\n\n\n 1\n', request_body
true
end.returns(successful_purchase_response)
From eae5215deedfb5659357bbca663cee1a63d61840 Mon Sep 17 00:00:00 2001
From: kylene-spreedly
Date: Thu, 4 May 2023 10:11:33 -0500
Subject: [PATCH 075/390] Adyen: Update Mastercard error messaging
Adyen error messaging uses the generic refusalReasonRaw
field as part of the response message. Adyen offers a more
detailed Mastercard-specific field called merchantAdviceCode
that should be present for failed Mastercard transactions.
Adyen error messaging now checks for the merchantAdviceCode first.
If it is not present (i.e. this is not a Mastercard transaction
or it is a Mastercard transaction and this field is missing for
some reason) then the default refusalReasonRaw field is used (like
previous functionality).
ECS-2767
Unit:
107 tests, 539 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
132 tests, 443 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
90.9091% passed
*These 12 tests fail on master
Closes #4770
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 4 +-
test/unit/gateways/adyen_test.rb | 47 +++++++++++++++++++
3 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9a0b249ed12..edb03685a64 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
+* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 3936ca9da0e..495afeb9dc4 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -703,8 +703,8 @@ def message_from(action, response)
end
def authorize_message_from(response)
- if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
- "#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
+ if response['refusalReason'] && response['additionalData'] && (response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw'])
+ "#{response['refusalReason']} | #{response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw']}"
else
response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
end
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 436c19cb223..864bf7d166d 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -324,6 +324,24 @@ def test_failed_authorise3ds2
assert_failure response
end
+ def test_failed_authorise_visa
+ @gateway.expects(:ssl_post).returns(failed_authorize_visa_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, {})
+
+ assert_equal 'Refused | 01: Refer to card issuer', response.message
+ assert_failure response
+ end
+
+ def test_failed_authorise_mastercard
+ @gateway.expects(:ssl_post).returns(failed_authorize_mastercard_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, {})
+
+ assert_equal 'Refused | 01 : New account information available', response.message
+ assert_failure response
+ end
+
def test_successful_capture
@gateway.expects(:ssl_post).returns(successful_capture_response)
response = @gateway.capture(@amount, '7914775043909934')
@@ -1655,6 +1673,35 @@ def failed_authorize_3ds2_response
RESPONSE
end
+ def failed_authorize_visa_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": "01: Refer to card issuer"
+ },
+ "refusalReason": "Refused",
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_mastercard_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": "01: Refer to card issuer",
+ "merchantAdviceCode": "01 : New account information available"
+ },
+ "refusalReason": "Refused",
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
def successful_capture_response
<<-RESPONSE
{
From ef18eb6fcc47c74e3eefbb9859618eed1bb9b005 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 12 May 2023 18:27:27 -0400
Subject: [PATCH 076/390] Authorize.net: update mapping for billing address
phone number
Adds a bit of logic to the Authorize.net gateway so that phone number can be passed via `billing_address[phone_number]` in addition to `billing_address[phone]` This is similar to #4138
CER-590
LOCAL
5503 tests, 77374 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
UNIT
121 tests, 681 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
REMOTE
84 tests, 301 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/authorize_net.rb | 3 ++-
test/remote/gateways/remote_authorize_net_test.rb | 10 ++++++++++
test/unit/gateways/authorize_net_test.rb | 14 ++++++++++++++
4 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index edb03685a64..86a9392c6e5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
+* Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb
index 6ce77e44789..9f14cd69783 100644
--- a/lib/active_merchant/billing/gateways/authorize_net.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net.rb
@@ -604,6 +604,7 @@ def add_billing_address(xml, payment_source, options)
first_name, last_name = names_from(payment_source, address, options)
state = state_from(address, options)
full_address = "#{address[:address1]} #{address[:address2]}".strip
+ phone = address[:phone] || address[:phone_number] || ''
xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
@@ -613,7 +614,7 @@ def add_billing_address(xml, payment_source, options)
xml.state(truncate(state, 40))
xml.zip(truncate((address[:zip] || options[:zip]), 20))
xml.country(truncate(address[:country], 60))
- xml.phoneNumber(truncate(address[:phone], 25)) unless empty?(address[:phone])
+ xml.phoneNumber(truncate(phone, 25)) unless empty?(phone)
xml.faxNumber(truncate(address[:fax], 25)) unless empty?(address[:fax])
end
end
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index 2dd8f4ef594..4a1237b1c14 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -414,6 +414,16 @@ def test_successful_authorization_with_moto_retail_type
assert response.authorization
end
+ def test_successful_purchase_with_phone_number
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '5554443210'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index 16aa5f4a4a6..37f0d710f34 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -940,6 +940,20 @@ def test_address
end.respond_with(successful_authorize_response)
end
+ def test_address_with_alternate_phone_number_field
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO', phone_number: '(555)555-5555', fax: '(555)555-4444' })
+ end.check_request do |_endpoint, data, _headers|
+ parse(data) do |doc|
+ assert_equal 'CO', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'US', doc.at_xpath('//billTo/country').content, data
+ assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content
+ assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content
+ end
+ end.respond_with(successful_authorize_response)
+ end
+
def test_address_with_empty_billing_address
stub_comms do
@gateway.authorize(@amount, @credit_card)
From 9d6e2040f620dcfd43b3ae845b487e732cbc21b9 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Mon, 15 May 2023 09:28:24 -0400
Subject: [PATCH 077/390] Braintree: update mapping for billing address phone
number
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds a bit of logic to the Braintree gateway so that phone number can be passed via billing_address[phone_number] in addition to billing_address[phone] This is similar to #4138
CER-603
LOCAL
5505 tests, 77373 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
UNIT
94 tests, 207 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
REMOTE
103 tests, 550 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/braintree_blue.rb | 15 ++++++++-------
.../remote/gateways/remote_braintree_blue_test.rb | 9 +++++++++
3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 86a9392c6e5..a35f15211bb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@
* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
* Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
+* Braintree: Update mapping for billing address phone number [jcreiff] #4779
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 6e1b4f00adf..c4ba7524fde 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -197,8 +197,7 @@ def update(vault_id, creditcard, options = {})
first_name: creditcard.first_name,
last_name: creditcard.last_name,
email: scrub_email(options[:email]),
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
- options[:billing_address][:phone]),
+ phone: phone_from(options),
credit_card: credit_card_params)
Response.new(result.success?, message_from_result(result),
braintree_customer: (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?),
@@ -267,8 +266,7 @@ def add_customer_with_credit_card(creditcard, options)
first_name: creditcard.first_name,
last_name: creditcard.last_name,
email: scrub_email(options[:email]),
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
- options[:billing_address][:phone]),
+ phone: phone_from(options),
id: options[:customer],
device_data: options[:device_data]
}.merge credit_card_params
@@ -348,6 +346,10 @@ def merge_credit_card_options(parameters, options)
parameters
end
+ def phone_from(options)
+ options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ end
+
def map_address(address)
mapped = {
street_address: address[:address1],
@@ -628,8 +630,7 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
customer: {
id: options[:store] == true ? '' : options[:store],
email: scrub_email(options[:email]),
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
- options[:billing_address][:phone])
+ phone: phone_from(options)
},
options: {
store_in_vault: options[:store] ? true : false,
@@ -932,7 +933,7 @@ def create_customer_from_bank_account(payment_method, options)
first_name: payment_method.first_name,
last_name: payment_method.last_name,
email: scrub_email(options[:email]),
- phone: options[:phone] || options.dig(:billing_address, :phone),
+ phone: phone_from(options),
device_data: options[:device_data]
}.compact
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index f97f6a73d1f..1cab2cd4ac6 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -491,6 +491,15 @@ def test_successful_purchase_with_phone_from_address
assert_equal '(555)555-5555', transaction['customer_details']['phone']
end
+ def test_successful_purchase_with_phone_number_from_address
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '9191231234'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ transaction = response.params['braintree_transaction']
+ assert_equal '9191231234', transaction['customer_details']['phone']
+ end
+
def test_successful_purchase_with_skip_advanced_fraud_checking_option
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_advanced_fraud_checking: true))
assert_success response
From 87f20ddfb3ebc633cc0460484970f23397616448 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Tue, 23 May 2023 12:10:56 -0500
Subject: [PATCH 078/390] CommerceHub: Enabling multi-use public key encryption
(#4771)
Summary:
------------------------------
Changes the payment method so is possible to send an encrypted
credit card following the CommerceHub Multi-User public key
methodology.
SER-555
Note on failing test: You need the proper account permissions and
credentials to use the encrypted credit card.
Remote Test:
------------------------------
Finished in 288.325843 seconds.
25 tests, 63 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 38.640945 seconds.
5506 tests, 77384 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
Co-authored-by: cristian
Co-authored-by: Nick Ashton
---
CHANGELOG | 2 ++
.../billing/gateways/commerce_hub.rb | 9 +++++--
.../gateways/remote_commerce_hub_test.rb | 20 +++++++++++---
test/unit/gateways/commerce_hub_test.rb | 27 +++++++++++++++++++
4 files changed, 53 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a35f15211bb..cd7c579193f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,11 +4,13 @@
== HEAD
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
+* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
* Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
* Braintree: Update mapping for billing address phone number [jcreiff] #4779
+* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index b552e3b4697..a3b8eae3237 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -114,7 +114,7 @@ def add_transaction_interaction(post, options)
post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
post[:transactionInteraction][:eciIndicator] = options[:eci_indicator] || 'CHANNEL_ENCRYPTED'
post[:transactionInteraction][:posConditionCode] = options[:pos_condition_code] || 'CARD_NOT_PRESENT_ECOM'
- post[:transactionInteraction][:posEntryMode] = options[:pos_entry_mode] || 'MANUAL'
+ post[:transactionInteraction][:posEntryMode] = (options[:pos_entry_mode] || 'MANUAL') unless options[:encryption_data].present?
post[:transactionInteraction][:additionalPosInformation] = {}
post[:transactionInteraction][:additionalPosInformation][:dataEntrySource] = options[:data_entry_source] || 'UNSPECIFIED'
end
@@ -256,7 +256,12 @@ def add_payment(post, payment, options = {})
when NetworkTokenizationCreditCard
add_decrypted_wallet(source, payment, options)
when CreditCard
- add_credit_card(source, payment, options)
+ if options[:encryption_data].present?
+ source[:sourceType] = 'PaymentCard'
+ source[:encryptionData] = options[:encryption_data]
+ else
+ add_credit_card(source, payment, options)
+ end
when String
add_payment_token(source, payment, options)
end
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index caf2667146f..9375a73c0b2 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -109,7 +109,7 @@ def test_successful_purchase_with_stored_credential_framework
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_equal 'Unable to assign card to brand: Invalid', response.message
assert_equal '104', response.error_code
end
@@ -131,7 +131,7 @@ def test_successful_authorize
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_equal 'Unable to assign card to brand: Invalid', response.message
end
def test_successful_authorize_and_void
@@ -235,7 +235,7 @@ def test_successful_purchase_with_apple_pay
def test_failed_purchase_with_declined_apple_pay
response = @gateway.purchase(@amount, @declined_apple_pay, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_equal 'Unable to assign card to brand: Invalid', response.message
end
def test_transcript_scrubbing
@@ -260,4 +260,18 @@ def test_transcript_scrubbing_apple_pay
assert_scrubbed(@gateway.options[:api_secret], transcript)
assert_scrubbed(@apple_pay.payment_cryptogram, transcript)
end
+
+ def test_successful_purchase_with_encrypted_credit_card
+ @options[:encryption_data] = {
+ keyId: '6d0b6b63-3658-4c90-b7a4-bffb8a928288',
+ encryptionType: 'RSA',
+ encryptionBlock: 'udJ89RebrHLVxa3ofdyiQ/RrF2Y4xKC/qw4NuV1JYrTDEpNeIq9ZimVffMjgkyKL8dlnB2R73XFtWA4klHrpn6LZrRumYCgoqAkBRJCrk09+pE5km2t2LvKtf/Bj2goYQNFA9WLCCvNGwhofp8bNfm2vfGsBr2BkgL+PH/M4SqyRHz0KGKW/NdQ4Mbdh4hLccFsPjtDnNidkMep0P02PH3Se6hp1f5GLkLTbIvDLPSuLa4eNgzb5/hBBxrq5M5+5n9a1PhQnVT1vPU0WbbWe1SGdGiVCeSYmmX7n+KkVmc1lw0dD7NXBjKmD6aGFAWGU/ls+7JVydedDiuz4E7HSDQ==',
+ encryptionBlockFields: 'card.cardData:16,card.nameOnCard:10,card.expirationMonth:2,card.expirationYear:4,card.securityCode:3',
+ encryptionTarget: 'MANUAL'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
end
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index 23491dcdfcc..e0c4e0ad5ef 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -51,6 +51,7 @@ def test_successful_purchase
assert_equal request['amount']['total'], (@amount / 100.0).to_f
assert_equal request['source']['card']['cardData'], @credit_card.number
assert_equal request['source']['card']['securityCode'], @credit_card.verification_value
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
assert_equal request['source']['card']['securityCodeIndicator'], 'PROVIDED'
end.respond_with(successful_purchase_response)
@@ -347,6 +348,32 @@ def test_add_reference_transaction_details_refund_reference_id
assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
end
+ def test_successful_purchase_when_encrypted_credit_card_present
+ @options[:order_id] = 'abc123'
+ @options[:encryption_data] = {
+ keyId: SecureRandom.uuid,
+ encryptionType: 'RSA',
+ encryptionBlock: SecureRandom.alphanumeric(20),
+ encryptionBlockFields: 'card.cardData:16,card.nameOnCard:8,card.expirationMonth:2,card.expirationYear:4,card.securityCode:3',
+ encryptionTarget: 'MANUAL'
+ }
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ refute_nil request['source']['encryptionData']
+ assert_equal request['source']['sourceType'], 'PaymentCard'
+ assert_equal request['source']['encryptionData']['keyId'], @options[:encryption_data][:keyId]
+ assert_equal request['source']['encryptionData']['encryptionType'], 'RSA'
+ assert_equal request['source']['encryptionData']['encryptionBlock'], @options[:encryption_data][:encryptionBlock]
+ assert_equal request['source']['encryptionData']['encryptionBlockFields'], @options[:encryption_data][:encryptionBlockFields]
+ assert_equal request['source']['encryptionData']['encryptionTarget'], 'MANUAL'
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
private
def successful_purchase_response
From 8607bf7ae722f707726d99c325c74be415bb9bdc Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Fri, 26 May 2023 08:18:20 -0500
Subject: [PATCH 079/390] Ogone: Enable 3ds Global for Ogone Gateway (#4776)
Description
-------------------------------------------
This commit introduces the support for 3DS Global payments using the Ogone Direct API through GlobalCollect.
As Ogone and GlobalCollect share the same underlying payment service provider (PSP), Worldline, we can leverage
the new attribute 'ogone_direct' to use the appropriate credentials and endpoint to connect with the Ogone Direct API.
[SER-562](https://spreedly.atlassian.net/browse/SER-562)
UNIT TEST
-------------------------------------------
Finished in 0.253826 seconds.
44 tests, 225 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
173.35 tests/s, 886.43 assertions/s
REMOTE TEST
-------------------------------------------
Finished in 71.318909 seconds.
43 tests, 115 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.6744% passed
0.60 tests/s, 1.61 assertions/s
Note:
During testing, a single failure related to installment processing was identified with GlobalCollect.
The error message "NO_ACQUIRER_CONFIGURED_FOR_INSTALLMENTS" I think that the issue may be related to
GlobalCollect's account configuration, which is outside the scope of this update.
RUBOCOP
-------------------------------------------
760 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 2 +-
.../billing/gateways/global_collect.rb | 60 ++++++---
test/fixtures.yml | 9 +-
.../gateways/remote_global_collect_test.rb | 117 +++++++++++++++++-
test/unit/gateways/global_collect_test.rb | 6 +-
5 files changed, 165 insertions(+), 29 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index cd7c579193f..0b4494c36b5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,13 +4,13 @@
== HEAD
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
-* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
* Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
* Braintree: Update mapping for billing address phone number [jcreiff] #4779
* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
+* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index c1a44930a29..7dc5ecb62cf 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -2,6 +2,8 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class GlobalCollectGateway < Gateway
class_attribute :preproduction_url
+ class_attribute :ogone_direct_test
+ class_attribute :ogone_direct_live
self.display_name = 'GlobalCollect'
self.homepage_url = 'http://www.globalcollect.com/'
@@ -9,6 +11,8 @@ class GlobalCollectGateway < Gateway
self.test_url = 'https://eu.sandbox.api-ingenico.com'
self.preproduction_url = 'https://world.preprod.api-ingenico.com'
self.live_url = 'https://world.api-ingenico.com'
+ self.ogone_direct_test = 'https://payment.preprod.direct.worldline-solutions.com'
+ self.ogone_direct_live = 'https://payment.direct.worldline-solutions.com'
self.supported_countries = %w[AD AE AG AI AL AM AO AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BW BY BZ CA CC CD CF CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HN HR HT HU ID IE IL IM IN IS IT JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LB LC LI LK LR LS LT LU LV MA MC MD ME MF MG MH MK MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PL PN PS PT PW QA RE RO RS RU RW SA SB SC SE SG SH SI SJ SK SL SM SN SR ST SV SZ TC TD TG TH TJ TL TM TN TO TR TT TV TW TZ UA UG US UY UZ VC VE VG VI VN WF WS ZA ZM ZW]
self.default_currency = 'USD'
@@ -114,7 +118,7 @@ def add_order(post, money, options, capture: false)
post['order']['references']['invoiceData'] = {
'invoiceNumber' => options[:invoice]
}
- add_airline_data(post, options)
+ add_airline_data(post, options) unless ogone_direct?
add_lodging_data(post, options)
add_number_of_installments(post, options) if options[:number_of_installments]
end
@@ -248,9 +252,10 @@ def add_creator_info(post, options)
end
def add_amount(post, money, options = {})
+ currency_ogone = 'EUR' if ogone_direct?
post['amountOfMoney'] = {
'amount' => amount(money),
- 'currencyCode' => options[:currency] || currency(money)
+ 'currencyCode' => options[:currency] || currency_ogone || currency(money)
}
end
@@ -262,7 +267,7 @@ def add_payment(post, payment, options)
product_id = options[:payment_product_id] || BRAND_MAP[payment.brand]
specifics_inputs = {
'paymentProductId' => product_id,
- 'skipAuthentication' => 'true', # refers to 3DSecure
+ 'skipAuthentication' => options[:skip_authentication] || 'true', # refers to 3DSecure
'skipFraudService' => 'true',
'authorizationMode' => pre_authorization
}
@@ -377,18 +382,24 @@ def add_fraud_fields(post, options)
def add_external_cardholder_authentication_data(post, options)
return unless threeds_2_options = options[:three_d_secure]
- authentication_data = {}
- authentication_data[:acsTransactionId] = threeds_2_options[:acs_transaction_id] if threeds_2_options[:acs_transaction_id]
- authentication_data[:cavv] = threeds_2_options[:cavv] if threeds_2_options[:cavv]
- authentication_data[:cavvAlgorithm] = threeds_2_options[:cavv_algorithm] if threeds_2_options[:cavv_algorithm]
- authentication_data[:directoryServerTransactionId] = threeds_2_options[:ds_transaction_id] if threeds_2_options[:ds_transaction_id]
- authentication_data[:eci] = threeds_2_options[:eci] if threeds_2_options[:eci]
- authentication_data[:threeDSecureVersion] = threeds_2_options[:version] if threeds_2_options[:version]
- authentication_data[:validationResult] = threeds_2_options[:authentication_response_status] if threeds_2_options[:authentication_response_status]
- authentication_data[:xid] = threeds_2_options[:xid] if threeds_2_options[:xid]
+ authentication_data = {
+ priorThreeDSecureData: { acsTransactionId: threeds_2_options[:acs_transaction_id] }.compact,
+ cavv: threeds_2_options[:cavv],
+ cavvAlgorithm: threeds_2_options[:cavv_algorithm],
+ directoryServerTransactionId: threeds_2_options[:ds_transaction_id],
+ eci: threeds_2_options[:eci],
+ threeDSecureVersion: threeds_2_options[:version] || options[:three_ds_version],
+ validationResult: threeds_2_options[:authentication_response_status],
+ xid: threeds_2_options[:xid],
+ acsTransactionId: threeds_2_options[:acs_transaction_id],
+ flow: threeds_2_options[:flow]
+ }.compact
post['cardPaymentMethodSpecificInput'] ||= {}
post['cardPaymentMethodSpecificInput']['threeDSecure'] ||= {}
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['merchantFraudRate'] = threeds_2_options[:merchant_fraud_rate]
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['exemptionRequest'] = threeds_2_options[:exemption_request]
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['secureCorporatePayment'] = threeds_2_options[:secure_corporate_payment]
post['cardPaymentMethodSpecificInput']['threeDSecure']['externalCardholderAuthenticationData'] = authentication_data unless authentication_data.empty?
end
@@ -402,17 +413,28 @@ def parse(body)
def url(action, authorization)
return preproduction_url + uri(action, authorization) if @options[:url_override].to_s == 'preproduction'
+ return ogone_direct_url(action, authorization) if ogone_direct?
(test? ? test_url : live_url) + uri(action, authorization)
end
+ def ogone_direct_url(action, authorization)
+ (test? ? ogone_direct_test : ogone_direct_live) + uri(action, authorization)
+ end
+
+ def ogone_direct?
+ @options[:url_override].to_s == 'ogone_direct'
+ end
+
def uri(action, authorization)
- uri = "/v1/#{@options[:merchant_id]}/"
+ version = ogone_direct? ? 'v2' : 'v1'
+ uri = "/#{version}/#{@options[:merchant_id]}/"
case action
when :authorize
uri + 'payments'
when :capture
- uri + "payments/#{authorization}/approve"
+ capture_name = ogone_direct? ? 'capture' : 'approve'
+ uri + "payments/#{authorization}/#{capture_name}"
when :refund
uri + "payments/#{authorization}/refund"
when :void
@@ -423,7 +445,7 @@ def uri(action, authorization)
end
def idempotency_key_for_signature(options)
- "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key] && !ogone_direct?
end
def commit(method, action, post, authorization: nil, options: {})
@@ -461,7 +483,7 @@ def headers(method, action, post, authorization = nil, options = {})
'Date' => date
}
- headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key] && !ogone_direct?
headers
end
@@ -474,13 +496,13 @@ def auth_digest(method, action, post, authorization = nil, options = {})
#{uri(action, authorization)}
REQUEST
data = data.each_line.reject { |line| line.strip == '' }.join
- digest = OpenSSL::Digest.new('sha256')
+ digest = OpenSSL::Digest.new('SHA256')
key = @options[:secret_api_key]
- "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
+ "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data)).strip}"
end
def date
- @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
+ @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
end
def content_type
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 2a9b3103e69..049b6a2fbb8 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -439,8 +439,13 @@ garanti:
global_collect:
merchant_id: 2196
- api_key_id: c91d6752cbbf9cf1
- secret_api_key: xHjQr5gL9Wcihkqoj4w/UQugdSCNXM2oUQHG5C82jy4=
+ api_key_id: b2311c2c832dd238
+ secret_api_key: Av5wKihoVlLN8SnGm6669hBHyG4Y4aS4KwaZUCvEIbY=
+
+global_collect_direct:
+ merchant_id: "NamastayTest"
+ api_key_id: "CF4CDF3F45F13C5CCBD0"
+ secret_api_key: "mvcEXR7Rem+KJE/atKsQ3Luqv37VEvTe2VOH5/Ibqd90VDzQ71Ht41RBVVyJuebzGnFu30dYpptgdrCcNvAu5A=="
global_transport:
global_user_name: "USERNAME"
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index 9c2553700bf..3a23595dd86 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -6,8 +6,12 @@ def setup
@gateway_preprod = GlobalCollectGateway.new(fixtures(:global_collect_preprod))
@gateway_preprod.options[:url_override] = 'preproduction'
+ @gateway_direct = GlobalCollectGateway.new(fixtures(:global_collect_direct))
+ @gateway_direct.options[:url_override] = 'ogone_direct'
+
@amount = 100
@credit_card = credit_card('4567350000427977')
+ @credit_card_challenge_3ds2 = credit_card('4874970686672022')
@naranja_card = credit_card('5895620033330020', brand: 'naranja')
@cabal_card = credit_card('6271701225979642', brand: 'cabal')
@declined_card = credit_card('5424180279791732')
@@ -63,6 +67,14 @@ def test_successful_purchase
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
+ def test_successful_purchase_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'EUR')
+ response = @gateway_direct.purchase(@accepted_amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'PENDING_CAPTURE', response.params['payment']['status']
+ end
+
def test_successful_purchase_with_naranja
options = @preprod_options.merge(requires_approval: false, currency: 'ARS')
response = @gateway_preprod.purchase(1000, @naranja_card, options)
@@ -145,8 +157,8 @@ def test_successful_purchase_with_more_options
end
def test_successful_purchase_with_installments
- options = @options.merge(number_of_installments: 2)
- response = @gateway.purchase(@amount, @credit_card, options)
+ options = @preprod_options.merge(number_of_installments: 2, currency: 'EUR')
+ response = @gateway_direct.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
end
@@ -159,14 +171,19 @@ def test_successful_purchase_with_requires_approval_true
response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
# When requires_approval is false, `purchase` will only make an `auth` call
# to request capture (and no subsequent `capture` call).
- def test_successful_purchase_with_requires_approval_false
+ def test_successful_purchase_with_requires_approval_false_ogone_direct
options = @options.merge(requires_approval: false)
+ response = @gateway_direct.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+ def test_successful_purchase_with_requires_approval_false
+ options = @options.merge(requires_approval: false)
response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
@@ -193,6 +210,56 @@ def test_successful_authorize_via_normalized_3ds2_fields
assert_equal 'Succeeded', response.message
end
+ def test_successful_authorize_via_3ds2_fields_direct_api
+ options = @options.merge(
+ currency: 'EUR',
+ phone: '5555555555',
+ three_d_secure: {
+ version: '2.1.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
+ acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
+ cavv_algorithm: 1,
+ authentication_response_status: 'Y',
+ challenge_indicator: 'no-challenge-requested',
+ flow: 'frictionless'
+ }
+ )
+
+ response = @gateway_direct.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_match 'PENDING_CAPTURE', response.params['payment']['status']
+ assert_match 'jJ81HADVRtXfCBATEp01CJUAAAA=', response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['threeDSecureResults']['cavv']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_via_3ds2_fields_direct_api_challenge
+ options = @options.merge(
+ currency: 'EUR',
+ phone: '5555555555',
+ is_recurring: true,
+ skip_authentication: false,
+ three_d_secure: {
+ version: '2.1.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
+ acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
+ cavv_algorithm: 1,
+ authentication_response_status: 'Y',
+ challenge_indicator: 'challenge-required'
+ }
+ )
+
+ response = @gateway_direct.purchase(@amount, @credit_card_challenge_3ds2, options)
+ assert_success response
+ assert_match 'CAPTURE_REQUESTED', response.params['status']
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_purchase_with_airline_data
options = @options.merge(
airline_data: {
@@ -321,6 +388,14 @@ def test_successful_purchase_with_blank_name
assert_equal 'Succeeded', response.message
end
+ def test_unsuccessful_purchase_with_blank_name_ogone_direct
+ credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil })
+
+ response = @gateway_direct.purchase(@amount, credit_card, @options)
+ assert_failure response
+ assert_equal 'PARAMETER_NOT_FOUND_IN_REQUEST', response.message
+ end
+
def test_successful_purchase_with_pre_authorization_flag
response = @gateway.purchase(@accepted_amount, @credit_card, @options.merge(pre_authorization: true))
assert_success response
@@ -347,6 +422,12 @@ def test_failed_purchase
assert_equal 'Not authorised', response.message
end
+ def test_failed_purchase_ogone_direct
+ response = @gateway_direct.purchase(@rejected_amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cardPaymentMethodSpecificInput.card.cardNumber does not match with cardPaymentMethodSpecificInput.paymentProductId.', response.message
+ end
+
def test_successful_authorize_and_capture
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -368,13 +449,18 @@ def test_failed_authorize
assert_equal 'Not authorised', response.message
end
+ def test_failed_authorize_ogone_direct
+ response = @gateway_direct.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cardPaymentMethodSpecificInput.card.cardNumber does not match with cardPaymentMethodSpecificInput.paymentProductId.', response.message
+ end
+
def test_partial_capture
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert capture = @gateway.capture(@amount - 1, auth.authorization)
assert_success capture
- assert_equal 99, capture.params['payment']['paymentOutput']['amountOfMoney']['amount']
end
def test_failed_capture
@@ -416,6 +502,15 @@ def test_successful_void
assert_equal 'Succeeded', void.message
end
+ def test_successful_void_ogone_direct
+ auth = @gateway_direct.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway_direct.void(auth.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
def test_failed_void
response = @gateway.void('123')
assert_failure response
@@ -450,12 +545,24 @@ def test_successful_verify
assert_equal 'Succeeded', response.message
end
+ def test_successful_verify_ogone_direct
+ response = @gateway_direct.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
assert_equal 'Not authorised', response.message
end
+ def test_failed_verify_ogone_direct
+ response = @gateway_direct.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal false, response.params['paymentResult']['payment']['statusOutput']['isAuthorized']
+ end
+
def test_invalid_login
gateway = GlobalCollectGateway.new(merchant_id: '', api_key_id: '', secret_api_key: '')
response = gateway.purchase(@amount, @credit_card, @options)
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index 0e888bff9d3..b6ce2a95df3 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -46,7 +46,8 @@ def setup
ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
cavv_algorithm: 1,
- authentication_response_status: 'Y'
+ authentication_response_status: 'Y',
+ flow: 'frictionless'
}
)
end
@@ -392,7 +393,8 @@ def test_successful_authorize_with_3ds_auth
response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@accepted_amount, @credit_card, @options_3ds2)
end.check_request do |_method, _endpoint, data, _headers|
- assert_match(/"threeDSecure\":{\"externalCardholderAuthenticationData\":{/, data)
+ assert_match(/threeDSecure/, data)
+ assert_match(/externalCardholderAuthenticationData/, data)
assert_match(/"eci\":\"05\"/, data)
assert_match(/"cavv\":\"jJ81HADVRtXfCBATEp01CJUAAAA=\"/, data)
assert_match(/"xid\":\"BwABBJQ1AgAAAAAgJDUCAAAAAAA=\"/, data)
From 19b7b4fc0426c22c9d770fcc34c17357c3256d42 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Tue, 30 May 2023 12:08:10 -0400
Subject: [PATCH 080/390] Borgun change default TrCurrencyExponent and
MerchantReturnUrl (#4788)
Borgun default TrCurrencyExponent to 2 for all 3DS txn and 0 for all
else. Change MerchantReturnUrl to `redirect_url`.
Unit: 11 tests, 56 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 22 tests, 43 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
77.2727% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/borgun.rb | 8 ++++----
test/remote/gateways/remote_borgun_test.rb | 4 ++--
test/unit/gateways/borgun_test.rb | 4 ++--
4 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0b4494c36b5..7ad7a297a71 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,7 @@
* Braintree: Update mapping for billing address phone number [jcreiff] #4779
* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
+* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
index a2681c59200..89942593395 100644
--- a/lib/active_merchant/billing/gateways/borgun.rb
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -105,17 +105,17 @@ def add_3ds_fields(post, options)
def add_3ds_preauth_fields(post, options)
post[:SaleDescription] = options[:sale_description] || ''
- post[:MerchantReturnURL] = options[:merchant_return_url] if options[:merchant_return_url]
+ post[:MerchantReturnURL] = options[:redirect_url] if options[:redirect_url]
end
def add_invoice(post, money, options)
post[:TrAmount] = amount(money)
post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
# The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
- if post[:TrCurrency] == '352' && options[:apply_3d_secure] == '1'
- post[:TrCurrencyExponent] = 2
- else
+ if post[:TrCurrency] == '352' && options[:apply_3d_secure] != '1'
post[:TrCurrencyExponent] = 0
+ else
+ post[:TrCurrencyExponent] = 2
end
post[:TerminalID] = options[:terminal_id] || '1'
end
diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb
index c6f2b5afbdb..1108632f95f 100644
--- a/test/remote/gateways/remote_borgun_test.rb
+++ b/test/remote/gateways/remote_borgun_test.rb
@@ -30,14 +30,14 @@ def test_successful_purchase
end
def test_successful_preauth_3ds
- response = @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
assert_success response
assert_equal 'Succeeded', response.message
assert_not_nil response.params['redirecttoacsform']
end
def test_successful_preauth_frictionless_3ds
- response = @gateway.purchase(@amount, @frictionless_3ds_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
+ response = @gateway.purchase(@amount, @frictionless_3ds_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
assert_success response
assert_equal 'Succeeded', response.message
assert_nil response.params['redirecttoacsform']
diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb
index 0bfc0dc51ed..546d6aaa249 100644
--- a/test/unit/gateways/borgun_test.rb
+++ b/test/unit/gateways/borgun_test.rb
@@ -58,9 +58,9 @@ def test_authorize_and_capture
def test_successful_preauth_3ds
response = stub_comms do
- @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
+ @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
end.check_request do |_endpoint, data, _headers|
- assert_match(/MerchantReturnURL>#{@options[:merchant_return_url]}/, data)
+ assert_match(/MerchantReturnURL>#{@options[:redirect_url]}/, data)
assert_match(/SaleDescription>#{@options[:sale_description]}/, data)
assert_match(/TrCurrencyExponent>2/, data)
end.respond_with(successful_get_3ds_authentication_response)
From 4ac3f2e12854ad7ceba068c380bf34af25f66e53 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Tue, 30 May 2023 15:05:01 -0400
Subject: [PATCH 081/390] Borgun support for GBP currency (#4789)
Add support to Borgun for GPB based on [ISO
4217](https://en.wikipedia.org/wiki/ISO_4217)
Unit:
Remote:
---
lib/active_merchant/billing/gateways/borgun.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
index 89942593395..1850425e697 100644
--- a/lib/active_merchant/billing/gateways/borgun.rb
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -96,6 +96,7 @@ def scrub(transcript)
CURRENCY_CODES['ISK'] = '352'
CURRENCY_CODES['EUR'] = '978'
CURRENCY_CODES['USD'] = '840'
+ CURRENCY_CODES['GBP'] = '826'
def add_3ds_fields(post, options)
post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id]
From 51c1f2d0333c53b89a8fb12189838694e5b09e6b Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 11 May 2023 12:57:46 -0500
Subject: [PATCH 082/390] Worlpay: Fix Google Pay
Ensure that we don't send cardHolderName if empty and that Google Pay
and Apple Pay fall into the network tokenization code path and not
the credit card path.
Remote Tests:
100 tests, 416 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
107 tests, 633 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/worldpay.rb | 9 +++----
test/remote/gateways/remote_worldpay_test.rb | 3 ++-
test/unit/gateways/worldpay_test.rb | 24 +++++++++++++++++++
4 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 7ad7a297a71..f9dfef646df 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@
* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4
+* Worldpay: Fix Google Pay [almalee24] #4774
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index da5ac218014..e25c6116d67 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -683,7 +683,8 @@ def add_card(xml, payment_method, options)
'year' => format(payment_method.year, :four_digits_year)
)
end
- xml.cardHolderName card_holder_name(payment_method, options)
+ name = card_holder_name(payment_method, options)
+ xml.cardHolderName name if name.present?
xml.cvc payment_method.verification_value
add_address(xml, (options[:billing_address] || options[:address]), options)
@@ -995,10 +996,10 @@ def payment_details(payment_method)
case payment_method
when String
token_type_and_details(payment_method)
- when NetworkTokenizationCreditCard
- { payment_type: :network_token }
else
- { payment_type: :credit }
+ type = payment_method.respond_to?(:source) ? :network_token : :credit
+
+ { payment_type: type }
end
end
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index dfa2fe11b68..d259afdf089 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -6,6 +6,7 @@ def setup
@cftgateway = WorldpayGateway.new(fixtures(:world_pay_gateway_cft))
@amount = 100
+ @year = (Time.now.year + 2).to_s[-2..-1].to_i
@credit_card = credit_card('4111111111111111')
@amex_card = credit_card('3714 496353 98431')
@elo_credit_card = credit_card('4514 1600 0000 0008',
@@ -17,7 +18,7 @@ def setup
brand: 'elo')
@credit_card_with_two_digits_year = credit_card('4111111111111111',
month: 10,
- year: 22)
+ year: @year)
@cabal_card = credit_card('6035220000000006')
@naranja_card = credit_card('5895620000000002')
@sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index 98a1e5dde3e..b864eb5c228 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -145,11 +145,35 @@ def setup
}
end
+ def test_payment_type_for_network_card
+ payment = @gateway.send(:payment_details, @nt_credit_card)[:payment_type]
+ assert_equal payment, :network_token
+ end
+
+ def test_payment_type_for_credit_card
+ payment = @gateway.send(:payment_details, @credit_card)[:payment_type]
+ assert_equal payment, :credit
+ end
+
def test_successful_authorize
response = stub_comms do
@gateway.authorize(@amount, @credit_card, @options)
end.check_request do |_endpoint, data, _headers|
assert_match(/4242424242424242/, data)
+ assert_match(/cardHolderName/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ assert_equal 'R50704213207145707', response.authorization
+ end
+
+ def test_successful_authorize_without_name
+ credit_card = credit_card('4242424242424242', first_name: nil, last_name: nil)
+ response = stub_comms do
+ @gateway.authorize(@amount, credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/4242424242424242/, data)
+ assert_no_match(/cardHolderName/, data)
+ assert_match(/VISA-SSL/, data)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'R50704213207145707', response.authorization
From 46f7bbc8b7b6afa16f26b6cc12f7c13b4d1d3ea3 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 16 May 2023 15:10:07 -0500
Subject: [PATCH 083/390] Stripe PI: Update paramters for creating of customer
Start sending address, shipping, phone and email when creating
a customer.
Remote Tests
84 tests, 396 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests
42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 120 +++++++++---------
.../remote_stripe_payment_intents_test.rb | 14 ++
.../gateways/stripe_payment_intents_test.rb | 11 +-
4 files changed, 79 insertions(+), 67 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f9dfef646df..702134b72e4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,7 @@
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4
* Worldpay: Fix Google Pay [almalee24] #4774
+* Stripe PI: Update parameters for creation of customer [almalee24] #4782
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 008726c08e8..8f130fe7867 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -76,22 +76,27 @@ def confirm_intent(intent_id, payment_method, options = {})
def create_payment_method(payment_method, options = {})
post_data = add_payment_method_data(payment_method, options)
-
options = format_idempotency_key(options, 'pm')
commit(:post, 'payment_methods', post_data, options)
end
def add_payment_method_data(payment_method, options = {})
- post_data = {}
- post_data[:type] = 'card'
- post_data[:card] = {}
- post_data[:card][:number] = payment_method.number
- post_data[:card][:exp_month] = payment_method.month
- post_data[:card][:exp_year] = payment_method.year
- post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
- add_billing_address(post_data, options)
- add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
- post_data
+ post = {
+ type: 'card',
+ card: {
+ number: payment_method.number,
+ exp_month: payment_method.month,
+ exp_year: payment_method.year
+ }
+ }
+
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
+ if billing = options[:billing_address] || options[:address]
+ post[:billing_details] = add_address(billing, options)
+ end
+
+ add_name_only(post, payment_method) if post[:billing_details].nil?
+ post
end
def add_payment_method_card_data_token(post_data, payment_method)
@@ -212,16 +217,7 @@ def store(payment_method, options = {})
result = add_payment_method_token(params, payment_method, options)
return result if result.is_a?(ActiveMerchant::Billing::Response)
- if options[:customer]
- customer_id = options[:customer]
- else
- post[:description] = options[:description] if options[:description]
- post[:email] = options[:email] if options[:email]
- options = format_idempotency_key(options, 'customer')
- post[:expand] = [:sources]
- customer = commit(:post, 'customers', post, options)
- customer_id = customer.params['id']
- end
+ customer_id = options[:customer] || customer(post, payment_method, options).params['id']
options = format_idempotency_key(options, 'attach')
attach_parameters = { customer: customer_id }
attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
@@ -231,6 +227,23 @@ def store(payment_method, options = {})
end
end
+ def customer(post, payment, options)
+ post[:description] = options[:description] if options[:description]
+ post[:expand] = [:sources]
+ post[:email] = options[:email]
+
+ if billing = options[:billing_address] || options[:address]
+ post.merge!(add_address(billing, options))
+ end
+
+ if shipping = options[:shipping_address]
+ post[:shipping] = add_address(shipping, options)
+ end
+
+ options = format_idempotency_key(options, 'customer')
+ commit(:post, 'customers', post, options)
+ end
+
def unstore(identification, options = {}, deprecated_options = {})
if identification.include?('pm_')
_, payment_method = identification.split('|')
@@ -478,30 +491,34 @@ def setup_future_usage(post, options = {})
def add_billing_address_for_card_tokenization(post, options = {})
return unless (billing = options[:billing_address] || options[:address])
- post[:card][:address_city] = billing[:city] if billing[:city]
- post[:card][:address_country] = billing[:country] if billing[:country]
- post[:card][:address_line1] = billing[:address1] if billing[:address1]
- post[:card][:address_line2] = billing[:address2] if billing[:address2]
- post[:card][:address_zip] = billing[:zip] if billing[:zip]
- post[:card][:address_state] = billing[:state] if billing[:state]
+ address = add_address(billing, options)
+ modify_address = address[:address].transform_keys { |k| k == :postal_code ? :address_zip : k.to_s.prepend('address_').to_sym }.merge!({ name: address[:name] })
+
+ post[:card].merge!(modify_address)
end
- def add_billing_address(post, options = {})
- return unless billing = options[:billing_address] || options[:address]
+ def add_shipping_address(post, options = {})
+ return unless shipping = options[:shipping_address]
- email = billing[:email] || options[:email]
+ post[:shipping] = add_address(shipping, options)
+ post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
+ post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
+ end
- post[:billing_details] = {}
- post[:billing_details][:address] = {}
- post[:billing_details][:address][:city] = billing[:city] if billing[:city]
- post[:billing_details][:address][:country] = billing[:country] if billing[:country]
- post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
- post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
- post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
- post[:billing_details][:address][:state] = billing[:state] if billing[:state]
- post[:billing_details][:email] = email if email
- post[:billing_details][:name] = billing[:name] if billing[:name]
- post[:billing_details][:phone] = billing[:phone] if billing[:phone]
+ def add_address(address, options)
+ {
+ address: {
+ city: address[:city],
+ country: address[:country],
+ line1: address[:address1],
+ line2: address[:address2],
+ postal_code: address[:zip],
+ state: address[:state]
+ }.compact,
+ email: address[:email] || options[:email],
+ phone: address[:phone] || address[:phone_number],
+ name: address[:name]
+ }.compact
end
def add_name_only(post, payment_method)
@@ -511,27 +528,6 @@ def add_name_only(post, payment_method)
post[:billing_details][:name] = name
end
- def add_shipping_address(post, options = {})
- return unless shipping = options[:shipping_address]
-
- post[:shipping] = {}
-
- # fields required by Stripe PI
- post[:shipping][:address] = {}
- post[:shipping][:address][:line1] = shipping[:address1]
- post[:shipping][:name] = shipping[:name]
-
- # fields considered optional by Stripe PI
- post[:shipping][:address][:city] = shipping[:city] if shipping[:city]
- post[:shipping][:address][:country] = shipping[:country] if shipping[:country]
- post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2]
- post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip]
- post[:shipping][:address][:state] = shipping[:state] if shipping[:state]
- post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number]
- post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
- post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
- end
-
def format_idempotency_key(options, suffix)
return options unless options[:idempotency_key]
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index b5c4a98d7ab..2e307b8ee3d 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -1208,6 +1208,20 @@ def test_successful_store_with_idempotency_key
assert_equal store1.params['id'], store2.params['id']
end
+ def test_successful_customer_creating
+ options = {
+ currency: 'GBP',
+ billing_address: address,
+ shipping_address: address
+ }
+ assert customer = @gateway.customer({}, @visa_card, options)
+
+ assert_equal customer.params['name'], 'Jim Smith'
+ assert_equal customer.params['phone'], '(555)555-5555'
+ assert_not_empty customer.params['shipping']
+ assert_not_empty customer.params['address']
+ end
+
def test_successful_store_with_false_validate_option
options = {
currency: 'GBP',
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 683642b0628..26c14e5661a 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -31,9 +31,7 @@ def setup
brand: 'visa',
eci: '05',
month: '09',
- year: '2030',
- first_name: 'Longbob',
- last_name: 'Longsen'
+ year: '2030'
)
@apple_pay = network_tokenization_credit_card(
@@ -411,8 +409,11 @@ def test_purchase_with_google_pay_with_billing_address
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @google_pay, options)
end.check_request do |_method, endpoint, data, _headers|
- assert_match('card[tokenization_method]=android_pay', data) if %r{/tokens}.match?(endpoint)
- assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint)
+ if %r{/tokens}.match?(endpoint)
+ assert_match('card[name]=Jim+Smith', data)
+ assert_match('card[tokenization_method]=android_pay', data)
+ assert_match('card[address_line1]=456+My+Street', data)
+ end
assert_match('wallet[type]=google_pay', data) if %r{/wallet}.match?(endpoint)
assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint)
end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address)
From ef8d6a79c5402418e4cdb5d880091cb0cd81e986 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Wed, 31 May 2023 14:02:41 -0500
Subject: [PATCH 084/390] Revert "Stripe PI: Update paramters for creating of
customer"
This reverts commit 46f7bbc8b7b6afa16f26b6cc12f7c13b4d1d3ea3.
---
CHANGELOG | 1 -
.../gateways/stripe_payment_intents.rb | 120 +++++++++---------
.../remote_stripe_payment_intents_test.rb | 14 --
.../gateways/stripe_payment_intents_test.rb | 11 +-
4 files changed, 67 insertions(+), 79 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 702134b72e4..f9dfef646df 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,7 +13,6 @@
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4
* Worldpay: Fix Google Pay [almalee24] #4774
-* Stripe PI: Update parameters for creation of customer [almalee24] #4782
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 8f130fe7867..008726c08e8 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -76,27 +76,22 @@ def confirm_intent(intent_id, payment_method, options = {})
def create_payment_method(payment_method, options = {})
post_data = add_payment_method_data(payment_method, options)
+
options = format_idempotency_key(options, 'pm')
commit(:post, 'payment_methods', post_data, options)
end
def add_payment_method_data(payment_method, options = {})
- post = {
- type: 'card',
- card: {
- number: payment_method.number,
- exp_month: payment_method.month,
- exp_year: payment_method.year
- }
- }
-
- post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
- if billing = options[:billing_address] || options[:address]
- post[:billing_details] = add_address(billing, options)
- end
-
- add_name_only(post, payment_method) if post[:billing_details].nil?
- post
+ post_data = {}
+ post_data[:type] = 'card'
+ post_data[:card] = {}
+ post_data[:card][:number] = payment_method.number
+ post_data[:card][:exp_month] = payment_method.month
+ post_data[:card][:exp_year] = payment_method.year
+ post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
+ add_billing_address(post_data, options)
+ add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
+ post_data
end
def add_payment_method_card_data_token(post_data, payment_method)
@@ -217,7 +212,16 @@ def store(payment_method, options = {})
result = add_payment_method_token(params, payment_method, options)
return result if result.is_a?(ActiveMerchant::Billing::Response)
- customer_id = options[:customer] || customer(post, payment_method, options).params['id']
+ if options[:customer]
+ customer_id = options[:customer]
+ else
+ post[:description] = options[:description] if options[:description]
+ post[:email] = options[:email] if options[:email]
+ options = format_idempotency_key(options, 'customer')
+ post[:expand] = [:sources]
+ customer = commit(:post, 'customers', post, options)
+ customer_id = customer.params['id']
+ end
options = format_idempotency_key(options, 'attach')
attach_parameters = { customer: customer_id }
attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
@@ -227,23 +231,6 @@ def store(payment_method, options = {})
end
end
- def customer(post, payment, options)
- post[:description] = options[:description] if options[:description]
- post[:expand] = [:sources]
- post[:email] = options[:email]
-
- if billing = options[:billing_address] || options[:address]
- post.merge!(add_address(billing, options))
- end
-
- if shipping = options[:shipping_address]
- post[:shipping] = add_address(shipping, options)
- end
-
- options = format_idempotency_key(options, 'customer')
- commit(:post, 'customers', post, options)
- end
-
def unstore(identification, options = {}, deprecated_options = {})
if identification.include?('pm_')
_, payment_method = identification.split('|')
@@ -491,34 +478,30 @@ def setup_future_usage(post, options = {})
def add_billing_address_for_card_tokenization(post, options = {})
return unless (billing = options[:billing_address] || options[:address])
- address = add_address(billing, options)
- modify_address = address[:address].transform_keys { |k| k == :postal_code ? :address_zip : k.to_s.prepend('address_').to_sym }.merge!({ name: address[:name] })
-
- post[:card].merge!(modify_address)
+ post[:card][:address_city] = billing[:city] if billing[:city]
+ post[:card][:address_country] = billing[:country] if billing[:country]
+ post[:card][:address_line1] = billing[:address1] if billing[:address1]
+ post[:card][:address_line2] = billing[:address2] if billing[:address2]
+ post[:card][:address_zip] = billing[:zip] if billing[:zip]
+ post[:card][:address_state] = billing[:state] if billing[:state]
end
- def add_shipping_address(post, options = {})
- return unless shipping = options[:shipping_address]
+ def add_billing_address(post, options = {})
+ return unless billing = options[:billing_address] || options[:address]
- post[:shipping] = add_address(shipping, options)
- post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
- post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
- end
+ email = billing[:email] || options[:email]
- def add_address(address, options)
- {
- address: {
- city: address[:city],
- country: address[:country],
- line1: address[:address1],
- line2: address[:address2],
- postal_code: address[:zip],
- state: address[:state]
- }.compact,
- email: address[:email] || options[:email],
- phone: address[:phone] || address[:phone_number],
- name: address[:name]
- }.compact
+ post[:billing_details] = {}
+ post[:billing_details][:address] = {}
+ post[:billing_details][:address][:city] = billing[:city] if billing[:city]
+ post[:billing_details][:address][:country] = billing[:country] if billing[:country]
+ post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
+ post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
+ post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
+ post[:billing_details][:address][:state] = billing[:state] if billing[:state]
+ post[:billing_details][:email] = email if email
+ post[:billing_details][:name] = billing[:name] if billing[:name]
+ post[:billing_details][:phone] = billing[:phone] if billing[:phone]
end
def add_name_only(post, payment_method)
@@ -528,6 +511,27 @@ def add_name_only(post, payment_method)
post[:billing_details][:name] = name
end
+ def add_shipping_address(post, options = {})
+ return unless shipping = options[:shipping_address]
+
+ post[:shipping] = {}
+
+ # fields required by Stripe PI
+ post[:shipping][:address] = {}
+ post[:shipping][:address][:line1] = shipping[:address1]
+ post[:shipping][:name] = shipping[:name]
+
+ # fields considered optional by Stripe PI
+ post[:shipping][:address][:city] = shipping[:city] if shipping[:city]
+ post[:shipping][:address][:country] = shipping[:country] if shipping[:country]
+ post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2]
+ post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip]
+ post[:shipping][:address][:state] = shipping[:state] if shipping[:state]
+ post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number]
+ post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
+ post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
+ end
+
def format_idempotency_key(options, suffix)
return options unless options[:idempotency_key]
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 2e307b8ee3d..b5c4a98d7ab 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -1208,20 +1208,6 @@ def test_successful_store_with_idempotency_key
assert_equal store1.params['id'], store2.params['id']
end
- def test_successful_customer_creating
- options = {
- currency: 'GBP',
- billing_address: address,
- shipping_address: address
- }
- assert customer = @gateway.customer({}, @visa_card, options)
-
- assert_equal customer.params['name'], 'Jim Smith'
- assert_equal customer.params['phone'], '(555)555-5555'
- assert_not_empty customer.params['shipping']
- assert_not_empty customer.params['address']
- end
-
def test_successful_store_with_false_validate_option
options = {
currency: 'GBP',
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 26c14e5661a..683642b0628 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -31,7 +31,9 @@ def setup
brand: 'visa',
eci: '05',
month: '09',
- year: '2030'
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
)
@apple_pay = network_tokenization_credit_card(
@@ -409,11 +411,8 @@ def test_purchase_with_google_pay_with_billing_address
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @google_pay, options)
end.check_request do |_method, endpoint, data, _headers|
- if %r{/tokens}.match?(endpoint)
- assert_match('card[name]=Jim+Smith', data)
- assert_match('card[tokenization_method]=android_pay', data)
- assert_match('card[address_line1]=456+My+Street', data)
- end
+ assert_match('card[tokenization_method]=android_pay', data) if %r{/tokens}.match?(endpoint)
+ assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint)
assert_match('wallet[type]=google_pay', data) if %r{/wallet}.match?(endpoint)
assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint)
end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address)
From 38635ca85e6dd2fad201f6f32f29eb403b108ca1 Mon Sep 17 00:00:00 2001
From: aenand
Date: Wed, 31 May 2023 09:59:11 -0400
Subject: [PATCH 085/390] Cybersource: auto void r230
ECS-2870
Cybersource transactions that fail with a reasonCode of `230` are
in a state where the gateway advises the merchant to decline but has
not declined it themselves. Instead the transaction is pending capture which
can create a mismatch in reporting.
This commit attempts to auto void transactions that have this response code
and a flag that indicates the merchants would like to auto void these kinds
of transactions.
Remote:
121 tests, 611 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
95.8678% passed
5 test failures on master as well
---
CHANGELOG | 3 +-
.../billing/gateways/cyber_source.rb | 10 +++++
test/unit/gateways/cyber_source_test.rb | 41 +++++++++++++++++++
3 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index f9dfef646df..6eda6891f38 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,8 +11,9 @@
* Braintree: Update mapping for billing address phone number [jcreiff] #4779
* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
-* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4
* Worldpay: Fix Google Pay [almalee24] #4774
+* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
+* CyberSource: Enable auto void on r230 [aenand] #4794
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 30c674e2f89..4b6ea9c724e 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -1053,6 +1053,8 @@ def commit(request, action, amount, options)
message = message_from(response)
authorization = success || in_fraud_review?(response) ? authorization_from(response, action, amount, options) : nil
+ message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
+
Response.new(success, message, response,
test: test?,
authorization: authorization,
@@ -1061,6 +1063,14 @@ def commit(request, action, amount, options)
cvv_result: response[:cvCode])
end
+ def auto_void?(authorization, response, message, options = {})
+ return message unless response[:reasonCode] == '230' && options[:auto_void_230]
+
+ response = void(authorization, options)
+ response&.success? ? message += ' - transaction has been auto-voided.' : message += ' - transaction could not be auto-voided.'
+ message
+ end
+
# Parse the SOAP response
# Technique inspired by the Paypal Gateway
def parse(xml)
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 0dcb9dcc4dd..53055ccd7c0 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -1442,6 +1442,38 @@ def test_invalid_field
assert_equal 'c:billTo/c:postalCode', response.params['invalidField']
end
+ def test_cvv_mismatch_successful_auto_void
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void).once.returns(ActiveMerchant::Billing::Response.new(true, 'Transaction successful'))
+
+ response = @gateway.authorize(@amount, credit_card, @options.merge!(auto_void_230: true))
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check - transaction has been auto-voided.', response.message
+ end
+
+ def test_cvv_mismatch
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void).never
+
+ response = @gateway.purchase(@amount, credit_card, @options)
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check', response.message
+ end
+
+ def test_cvv_mismatch_auto_void_failed
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void)
+ response = @gateway.purchase(@amount, credit_card, @options.merge!(auto_void_230: true))
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check - transaction could not be auto-voided.', response.message
+ end
+
def test_able_to_properly_handle_40bytes_cryptogram
long_cryptogram = "NZwc40C4eTDWHVDXPekFaKkNYGk26w+GYDZmU50cATbjqOpNxR/eYA==\n"
credit_card = network_tokenization_credit_card('4111111111111111',
@@ -1822,6 +1854,15 @@ def invalid_field_response
XML
end
+ def cvv_mismatch_response
+ <<~XML
+
+
+ 2019-09-05T14:10:46.665Z5676926465076767004068REJECT230c:billTo/c:postalCodeAhjzbwSTM78uTleCsJWkEAJRqivRidukDssiQgRm0ky3SA7oegDUiwLm
+
+ XML
+ end
+
def invalid_xml_response
"What's all this then, govna?
"
end
From 9c309870fcb00490c7065e8619043c7f1bbc0136 Mon Sep 17 00:00:00 2001
From: Britney Smith
Date: Thu, 25 May 2023 11:42:32 -0400
Subject: [PATCH 086/390] Redsys: Set appropriate request fields for stored
credentials with CITs and MITs
Following pre-determined guidance for CIT/MIT request fields for this gateway. We were getting it mostly right, but almost didn't count, so the `DS_MERCHANT_DIRECTPAYMENT=false` value was added for initial CITs.
Both CITs and MITs should be indicated with the `stored_credential` field `recurring`, so as long as that happens, `DS_MERCHANT_COF_TYPE` should have the value as `R` in both transactions.
An outstanding task is to pass `DS_MERCHANT_IDENTIFIER` as 'REQUIRED' for CITs. I'm currently blocked on the credentials for the Redsys sandbox environment (getting the error `SIS0042` - Signature calculation error). This means that I'm unable to confirm that Redsys indeed returns a `DS_MERCHANT_IDENTIFIER` value in their response, in order to pass in a future MIT.
Test Summary
Local: 5512 tests, 77418 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit: 35 tests, 122 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/redsys.rb | 1 +
test/unit/gateways/redsys_test.rb | 126 +++++++++++++++++-
3 files changed, 126 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 6eda6891f38..4eeaee30267 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,7 @@
* Worldpay: Fix Google Pay [almalee24] #4774
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
* CyberSource: Enable auto void on r230 [aenand] #4794
+* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb
index 00239bf2ee2..80217a94247 100644
--- a/lib/active_merchant/billing/gateways/redsys.rb
+++ b/lib/active_merchant/billing/gateways/redsys.rb
@@ -538,6 +538,7 @@ def build_merchant_data(xml, data, options = {})
xml.DS_MERCHANT_COF_INI data[:DS_MERCHANT_COF_INI]
xml.DS_MERCHANT_COF_TYPE data[:DS_MERCHANT_COF_TYPE]
xml.DS_MERCHANT_COF_TXNID data[:DS_MERCHANT_COF_TXNID] if data[:DS_MERCHANT_COF_TXNID]
+ xml.DS_MERCHANT_DIRECTPAYMENT 'false' if options[:stored_credential][:initial_transaction]
end
end
end
diff --git a/test/unit/gateways/redsys_test.rb b/test/unit/gateways/redsys_test.rb
index 7d87a88a96a..8351babe5c1 100644
--- a/test/unit/gateways/redsys_test.rb
+++ b/test/unit/gateways/redsys_test.rb
@@ -59,7 +59,7 @@ def test_successful_purchase_with_stored_credentials
assert_success initial_res
assert_equal 'Transaction Approved', initial_res.message
assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid']
- network_transaction_id = initial_res.params['Ds_Merchant_Cof_Txnid']
+ network_transaction_id = initial_res.params['ds_merchant_cof_txnid']
@gateway.expects(:ssl_post).returns(successful_purchase_used_stored_credential_response)
used_options = {
@@ -76,6 +76,128 @@ def test_successful_purchase_with_stored_credentials
assert_equal '561350', res.params['ds_authorisationcode']
end
+ def test_successful_purchase_with_stored_credentials_for_merchant_initiated_transactions
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes(CGI.escape('0')),
+ includes(CGI.escape('1001')),
+ includes(CGI.escape('123')),
+ includes(CGI.escape('S')),
+ includes(CGI.escape('R')),
+ includes(CGI.escape('4242424242424242')),
+ includes(CGI.escape('2409')),
+ includes(CGI.escape('123')),
+ includes(CGI.escape('false')),
+ Not(includes(CGI.escape(''))),
+ Not(includes(CGI.escape('')))
+ ),
+ anything
+ ).returns(successful_purchase_initial_stored_credential_response)
+
+ initial_options = @options.merge(
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'recurring'
+ }
+ )
+ initial_res = @gateway.purchase(123, credit_card, initial_options)
+ assert_success initial_res
+ assert_equal 'Transaction Approved', initial_res.message
+ assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid']
+ network_transaction_id = initial_res.params['ds_merchant_cof_txnid']
+
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes('0'),
+ includes('1002'),
+ includes('123'),
+ includes('N'),
+ includes('R'),
+ includes('4242424242424242'),
+ includes('2409'),
+ includes('123'),
+ includes('true'),
+ includes('MIT'),
+ includes("#{network_transaction_id}")
+ ),
+ anything
+ ).returns(successful_purchase_used_stored_credential_response)
+ used_options = {
+ order_id: '1002',
+ sca_exemption: 'MIT',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'recurring',
+ network_transaction_id: network_transaction_id
+ }
+ }
+ res = @gateway.purchase(123, credit_card, used_options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '561350', res.params['ds_authorisationcode']
+ end
+
+ def test_successful_purchase_with_stored_credentials_for_merchant_initiated_transactions_with_card_tokens
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes(CGI.escape('0')),
+ includes(CGI.escape('1001')),
+ includes(CGI.escape('123')),
+ includes(CGI.escape('S')),
+ includes(CGI.escape('R')),
+ includes(CGI.escape('77bff3a969d6f97b2ec815448cdcff453971f573')),
+ includes(CGI.escape('false')),
+ Not(includes(CGI.escape(''))),
+ Not(includes(CGI.escape('')))
+ ),
+ anything
+ ).returns(successful_purchase_initial_stored_credential_response)
+
+ initial_options = @options.merge(
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'recurring'
+ }
+ )
+ initial_res = @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', initial_options)
+ assert_success initial_res
+ assert_equal 'Transaction Approved', initial_res.message
+ assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid']
+ network_transaction_id = initial_res.params['ds_merchant_cof_txnid']
+
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes('0'),
+ includes('1002'),
+ includes('123'),
+ includes('N'),
+ includes('R'),
+ includes('77bff3a969d6f97b2ec815448cdcff453971f573'),
+ includes('true'),
+ includes('MIT'),
+ includes("#{network_transaction_id}")
+ ),
+ anything
+ ).returns(successful_purchase_used_stored_credential_response)
+ used_options = {
+ order_id: '1002',
+ sca_exemption: 'MIT',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'recurring',
+ network_transaction_id: network_transaction_id
+ }
+ }
+ res = @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', used_options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '561350', res.params['ds_authorisationcode']
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_post).returns(failed_purchase_response)
res = @gateway.purchase(123, credit_card, @options)
@@ -301,7 +423,7 @@ def successful_purchase_response_with_credit_card_token
end
def successful_purchase_initial_stored_credential_response
- "00.11239781001989D357BCC9EF0962A456C51422C4FAF4BF4399F9195271310000561350A01724201210212202013"
+ "00.11239781001989D357BCC9EF0962A456C51422C4FAF4BF4399F9195271310000561350A0177bff3a969d6f97b2ec815448cdcff453971f573724201210212202013"
end
def successful_purchase_used_stored_credential_response
From 56d6a47ccd8a88fbd288ab781d45be6c2b4ccca7 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 5 Jun 2023 16:43:45 -0500
Subject: [PATCH 087/390] Stripe & Stripe PI: Validate API Key
Stripe Unit:
145 tests, 765 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Stripe Remote:
77 tests, 362 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Stripe PI Unit:
42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Stripe PI Remote:
83 tests, 391 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/stripe.rb | 15 ++++++++++-----
.../gateways/stripe_payment_intents_test.rb | 16 +++++++++++++++-
test/unit/gateways/stripe_test.rb | 18 ++++++++++++++++--
test/unit/gateways/webpay_test.rb | 2 +-
5 files changed, 43 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 4eeaee30267..9fe8c6c84b9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,7 @@
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
* CyberSource: Enable auto void on r230 [aenand] #4794
* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
+* Stripe & Stripe PI: Validate API Key [almalee24] #4801
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index e85d0ece147..10f7943f2c0 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -652,18 +652,19 @@ def flatten_array(flattened, array, prefix)
end
end
- def headers(options = {})
- key = options[:key] || @api_key
- idempotency_key = options[:idempotency_key]
+ def key(options = {})
+ options[:key] || @api_key
+ end
+ def headers(options = {})
headers = {
- 'Authorization' => 'Basic ' + Base64.strict_encode64(key.to_s + ':').strip,
+ 'Authorization' => 'Basic ' + Base64.strict_encode64(key(options).to_s + ':').strip,
'User-Agent' => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
'Stripe-Version' => api_version(options),
'X-Stripe-Client-User-Agent' => stripe_client_user_agent(options),
'X-Stripe-Client-User-Metadata' => { ip: options[:ip] }.to_json
}
- headers['Idempotency-Key'] = idempotency_key if idempotency_key
+ headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
headers['Stripe-Account'] = options[:stripe_account] if options[:stripe_account]
headers
end
@@ -694,6 +695,10 @@ def api_request(method, endpoint, parameters = nil, options = {})
def commit(method, url, parameters = nil, options = {})
add_expand_parameters(parameters, options) if parameters
+
+ return Response.new(false, 'Invalid API Key provided') if test? && !key(options).start_with?('sk_test')
+ return Response.new(false, 'Invalid API Key provided') if !test? && !key(options).start_with?('sk_live')
+
response = api_request(method, url, parameters, options)
response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
success = success_from(response, options)
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 683642b0628..66bdabfdc4f 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -4,7 +4,7 @@ class StripePaymentIntentsTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = StripePaymentIntentsGateway.new(login: 'login')
+ @gateway = StripePaymentIntentsGateway.new(login: 'sk_test_login')
@credit_card = credit_card()
@threeds_2_card = credit_card('4000000000003220')
@@ -259,6 +259,20 @@ def test_failed_payment_methods_post
assert_equal 'invalid_request_error', create.params.dig('error', 'type')
end
+ def test_invalid_login_test_transaction
+ gateway = StripePaymentIntentsGateway.new(login: 'sk_live_3422')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid API Key provided', response.message
+ end
+
+ def test_invalid_login_live_transaction
+ gateway = StripePaymentIntentsGateway.new(login: 'sk_test_3422', test: false)
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid API Key provided', response.message
+ end
+
def test_failed_error_on_requires_action
@gateway.expects(:ssl_request).returns(failed_with_set_error_on_requires_action_response)
diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb
index ea600f5fab3..68a760a409e 100644
--- a/test/unit/gateways/stripe_test.rb
+++ b/test/unit/gateways/stripe_test.rb
@@ -4,7 +4,7 @@ class StripeTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = StripeGateway.new(login: 'login')
+ @gateway = StripeGateway.new(login: 'sk_test_login')
@credit_card = credit_card()
@threeds_card = credit_card('4000000000003063')
@@ -876,6 +876,20 @@ def test_invalid_raw_response
assert_match(/^Invalid response received from the Stripe API/, response.message)
end
+ def test_invalid_login_test_transaction
+ gateway = StripeGateway.new(login: 'sk_live_3422')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid API Key provided', response.message
+ end
+
+ def test_invalid_login_live_transaction
+ gateway = StripePaymentIntentsGateway.new(login: 'sk_test_3422', test: false)
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid API Key provided', response.message
+ end
+
def test_add_creditcard_with_credit_card
post = {}
@gateway.send(:add_creditcard, post, @credit_card, {})
@@ -1254,7 +1268,7 @@ def test_optional_idempotency_on_verify
end
def test_initialize_gateway_with_version
- @gateway = StripeGateway.new(login: 'login', version: '2013-12-03')
+ @gateway = StripeGateway.new(login: 'sk_test_login', version: '2013-12-03')
@gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers|
headers && headers['Stripe-Version'] == '2013-12-03'
}.returns(successful_purchase_response)
diff --git a/test/unit/gateways/webpay_test.rb b/test/unit/gateways/webpay_test.rb
index 474479742f1..66176804c87 100644
--- a/test/unit/gateways/webpay_test.rb
+++ b/test/unit/gateways/webpay_test.rb
@@ -4,7 +4,7 @@ class WebpayTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = WebpayGateway.new(login: 'login')
+ @gateway = WebpayGateway.new(login: 'sk_test_login')
@credit_card = credit_card()
@amount = 40000
From 4946d603bd4854d4139c1ec29999fe3841500949 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Wed, 7 Jun 2023 11:42:39 -0500
Subject: [PATCH 088/390] Remove last validation for Stripe API Key
---
lib/active_merchant/billing/gateways/stripe.rb | 1 -
test/unit/gateways/stripe_payment_intents_test.rb | 7 -------
test/unit/gateways/stripe_test.rb | 7 -------
3 files changed, 15 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index 10f7943f2c0..325845ef7dc 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -697,7 +697,6 @@ def commit(method, url, parameters = nil, options = {})
add_expand_parameters(parameters, options) if parameters
return Response.new(false, 'Invalid API Key provided') if test? && !key(options).start_with?('sk_test')
- return Response.new(false, 'Invalid API Key provided') if !test? && !key(options).start_with?('sk_live')
response = api_request(method, url, parameters, options)
response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 66bdabfdc4f..8cdb96381bf 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -266,13 +266,6 @@ def test_invalid_login_test_transaction
assert_match 'Invalid API Key provided', response.message
end
- def test_invalid_login_live_transaction
- gateway = StripePaymentIntentsGateway.new(login: 'sk_test_3422', test: false)
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match 'Invalid API Key provided', response.message
- end
-
def test_failed_error_on_requires_action
@gateway.expects(:ssl_request).returns(failed_with_set_error_on_requires_action_response)
diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb
index 68a760a409e..deaee1aea6d 100644
--- a/test/unit/gateways/stripe_test.rb
+++ b/test/unit/gateways/stripe_test.rb
@@ -883,13 +883,6 @@ def test_invalid_login_test_transaction
assert_match 'Invalid API Key provided', response.message
end
- def test_invalid_login_live_transaction
- gateway = StripePaymentIntentsGateway.new(login: 'sk_test_3422', test: false)
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match 'Invalid API Key provided', response.message
- end
-
def test_add_creditcard_with_credit_card
post = {}
@gateway.send(:add_creditcard, post, @credit_card, {})
From b21feaf1f4cffaa085c7c784bf8b7655a4cdc90d Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 2 Jun 2023 11:26:26 -0400
Subject: [PATCH 089/390] Add BIN for Maestro
Adds one new BIN to the Maestro BIN list: 501623
CER-640
5514 tests, 77426 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/credit_card_methods.rb | 2 +-
test/unit/credit_card_methods_test.rb | 4 ++--
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9fe8c6c84b9..ffa77cd8d76 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@
* CyberSource: Enable auto void on r230 [aenand] #4794
* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
* Stripe & Stripe PI: Validate API Key [almalee24] #4801
+* Add BIN for Maestro [jcreiff] #4799
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 33611bcab3a..6d32a6a01d7 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -109,7 +109,7 @@ module CreditCardMethods
MAESTRO_BINS = Set.new(
%w[ 500057
501018 501043 501045 501047 501049 501051 501072 501075 501083 501087 501089 501095
- 501500
+ 501500 501623
501879 502113 502120 502121 502301
503175 503337 503645 503670
504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index eb4dfcf3a9c..b4cf9b49c1a 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -13,7 +13,7 @@ def maestro_card_numbers
6390000000000000 6390700000000000 6390990000000000
6761999999999999 6763000000000000 6799999999999999
5000330000000000 5811499999999999 5010410000000000
- 5010630000000000 5892440000000000
+ 5010630000000000 5892440000000000 5016230000000000
]
end
@@ -28,7 +28,7 @@ def non_maestro_card_numbers
def maestro_bins
%w[500032 500057 501015 501016 501018 501020 501021 501023 501024 501025 501026 501027 501028 501029
501038 501039 501040 501041 501043 501045 501047 501049 501051 501053 501054 501055 501056 501057
- 501058 501060 501061 501062 501063 501066 501067 501072 501075 501083 501087
+ 501058 501060 501061 501062 501063 501066 501067 501072 501075 501083 501087 501623
501800 501089 501091 501092 501095 501104 501105 501107 501108 501500 501879
502000 502113 502301 503175 503645 503800
503670 504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
From 98ee03eda59d7ce49961c5e3d92279a1c4fc338a Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Thu, 25 May 2023 11:52:19 -0700
Subject: [PATCH 090/390] DLocal: Add save field on card object
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/d_local.rb | 1 +
test/remote/gateways/remote_d_local_test.rb | 9 +++++++++
test/unit/gateways/d_local_test.rb | 8 ++++++++
4 files changed, 19 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index ffa77cd8d76..7296ab79a49 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@
* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
* Stripe & Stripe PI: Validate API Key [almalee24] #4801
* Add BIN for Maestro [jcreiff] #4799
+* D_Local: Add save field on card object [yunnydang] #4805
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/d_local.rb b/lib/active_merchant/billing/gateways/d_local.rb
index 2f0b959f592..6a172e77ee4 100644
--- a/lib/active_merchant/billing/gateways/d_local.rb
+++ b/lib/active_merchant/billing/gateways/d_local.rb
@@ -188,6 +188,7 @@ def add_card(post, card, action, options = {})
post[:card][:installments] = options[:installments] if options[:installments]
post[:card][:installments_id] = options[:installments_id] if options[:installments_id]
post[:card][:force_type] = options[:force_type].to_s.upcase if options[:force_type]
+ post[:card][:save] = options[:save] if options[:save]
end
def parse(body)
diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb
index 001817efef5..bd03a545a83 100644
--- a/test/remote/gateways/remote_d_local_test.rb
+++ b/test/remote/gateways/remote_d_local_test.rb
@@ -50,6 +50,15 @@ def test_successful_purchase
assert_match 'The payment was paid', response.message
end
+ def test_successful_purchase_with_save_option
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(save: true))
+ assert_success response
+ assert_equal true, response.params['card']['save']
+ assert_equal 'CREDIT', response.params['card']['type']
+ assert_not_empty response.params['card']['card_id']
+ assert_match 'The payment was paid', response.message
+ end
+
def test_successful_purchase_with_network_tokens
credit_card = network_tokenization_credit_card('4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
diff --git a/test/unit/gateways/d_local_test.rb b/test/unit/gateways/d_local_test.rb
index 33ea7361c74..d5eb9c9322c 100644
--- a/test/unit/gateways/d_local_test.rb
+++ b/test/unit/gateways/d_local_test.rb
@@ -36,6 +36,14 @@ def test_successful_purchase
assert response.test?
end
+ def test_purchase_with_save
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(save: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal true, JSON.parse(data)['card']['save']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_post).returns(failed_purchase_response)
From 88943ff38d5b51ee66e7cd8cdd4cf20927a0ee9b Mon Sep 17 00:00:00 2001
From: Daniel Herzog
Date: Mon, 12 Jun 2023 12:49:35 +0100
Subject: [PATCH 091/390] Add support for MsgSubID on PayPal Express requests
(#4798)
* Adds support for PayPal Express MsgSubID property
* Removes unrequired PayPal changes for MsgSubID
* Updates CHANGELOG with HEAD changes
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/paypal_express.rb | 2 ++
test/unit/gateways/paypal_express_test.rb | 10 ++++++++++
3 files changed, 13 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 7296ab79a49..c07c19b468a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,7 @@
* Stripe & Stripe PI: Validate API Key [almalee24] #4801
* Add BIN for Maestro [jcreiff] #4799
* D_Local: Add save field on card object [yunnydang] #4805
+* PayPal Express: Adds support for MsgSubID property on DoReferenceTransaction and DoExpressCheckoutPayment [wikiti] #4798
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/paypal_express.rb b/lib/active_merchant/billing/gateways/paypal_express.rb
index aaa65cec7e2..597cfeda9c3 100644
--- a/lib/active_merchant/billing/gateways/paypal_express.rb
+++ b/lib/active_merchant/billing/gateways/paypal_express.rb
@@ -108,6 +108,7 @@ def build_sale_or_authorization_request(action, money, options)
xml.tag! 'n2:PaymentAction', action
xml.tag! 'n2:Token', options[:token]
xml.tag! 'n2:PayerID', options[:payer_id]
+ xml.tag! 'n2:MsgSubID', options[:idempotency_key] if options[:idempotency_key]
add_payment_details(xml, money, currency_code, options)
end
end
@@ -251,6 +252,7 @@ def build_reference_transaction_request(action, money, options)
add_payment_details(xml, money, currency_code, options)
xml.tag! 'n2:IPAddress', options[:ip]
xml.tag! 'n2:MerchantSessionId', options[:merchant_session_id] if options[:merchant_session_id].present?
+ xml.tag! 'n2:MsgSubID', options[:idempotency_key] if options[:idempotency_key]
end
end
end
diff --git a/test/unit/gateways/paypal_express_test.rb b/test/unit/gateways/paypal_express_test.rb
index 195df830eaa..afd7fb5cbf0 100644
--- a/test/unit/gateways/paypal_express_test.rb
+++ b/test/unit/gateways/paypal_express_test.rb
@@ -773,6 +773,16 @@ def test_structure_correct
assert_equal [], schema.validate(sub_doc)
end
+ def test_build_reference_transaction_sets_idempotency_key
+ request = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Authorization', 100, idempotency_key: 'idempotency_key'))
+ assert_equal 'idempotency_key', REXML::XPath.first(request, '//n2:DoReferenceTransactionRequestDetails/n2:MsgSubID').text
+ end
+
+ def test_build_sale_or_authorization_request_sets_idempotency_key
+ request = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Authorization', 100, idempotency_key: 'idempotency_key'))
+ assert_equal 'idempotency_key', REXML::XPath.first(request, '//n2:DoExpressCheckoutPaymentRequestDetails/n2:MsgSubID').text
+ end
+
private
def successful_create_billing_agreement_response
From 8236186c947d4d40dfa1fc625d0df619ca5024d2 Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Mon, 12 Jun 2023 14:05:55 -0400
Subject: [PATCH 092/390] Checkout_v2: use `credit_card?`, not case equality
with `CreditCard` (#4803)
Checkout_v2: use `credit_card?`, not case equality with `CreditCard`
---
.../billing/gateways/checkout_v2.rb | 2 +-
test/unit/gateways/checkout_v2_test.rb | 32 +++++++++++++++++++
2 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index ba9323d0d84..59c68e8272f 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -185,7 +185,7 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:token_type] = token_type
post[key][:cryptogram] = cryptogram if cryptogram
post[key][:eci] = eci if eci
- when CreditCard
+ when ->(pm) { pm.try(:credit_card?) }
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index ed0269243db..f0f59a7d6f7 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -805,6 +805,38 @@ def test_add_shipping_address
assert_equal 'Succeeded', response.message
end
+ def test_purchase_supports_alternate_credit_card_implementation
+ alternate_credit_card_class = Class.new
+ alternate_credit_card = alternate_credit_card_class.new
+
+ alternate_credit_card.expects(:credit_card?).returns(true)
+ alternate_credit_card.expects(:name).returns(@credit_card.name)
+ alternate_credit_card.expects(:number).returns(@credit_card.number)
+ alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
+ alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
+ alternate_credit_card.expects(:last_name).at_least_once.returns(@credit_card.first_name)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, alternate_credit_card)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_supports_alternate_credit_card_implementation
+ alternate_credit_card_class = Class.new
+ alternate_credit_card = alternate_credit_card_class.new
+
+ alternate_credit_card.expects(:credit_card?).returns(true)
+ alternate_credit_card.expects(:name).returns(@credit_card.name)
+ alternate_credit_card.expects(:number).returns(@credit_card.number)
+ alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
+ alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
+ alternate_credit_card.expects(:last_name).at_least_once.returns(@credit_card.first_name)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, alternate_credit_card)
+ end.respond_with(successful_authorize_response)
+ end
+
private
def pre_scrubbed
From 29e42a227c2d00e59c9aa73910a7a4332db5721e Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Tue, 13 Jun 2023 08:58:35 -0500
Subject: [PATCH 093/390] Shift4: Enable general credit feature. (#4790)
Summary:------------------------------
Enabling general credit feature
Remote Test:
------------------------------
Finished in 171.436961 seconds.
28 tests, 63 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 92.8571% passed
failing test not related to PR changes
Unit Tests:
------------------------------
26 tests, 163 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detected
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/shift4.rb | 7 +++++--
test/remote/gateways/remote_shift4_test.rb | 12 ++++++++++++
test/unit/gateways/shift4_test.rb | 12 ++++++++++++
4 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c07c19b468a..0c7e5085f4b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,7 @@
* Add BIN for Maestro [jcreiff] #4799
* D_Local: Add save field on card object [yunnydang] #4805
* PayPal Express: Adds support for MsgSubID property on DoReferenceTransaction and DoExpressCheckoutPayment [wikiti] #4798
+* Shift4: Enable general credit feature [jherreraa] #4790
== Version 1.129.0 (May 3rd, 2023)
* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 6877f8e499a..ca6feaa5eaf 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -91,7 +91,7 @@ def capture(money, authorization, options = {})
commit(action, post, options)
end
- def refund(money, authorization, options = {})
+ def refund(money, payment_method, options = {})
post = {}
action = 'refund'
@@ -99,12 +99,15 @@ def refund(money, authorization, options = {})
add_invoice(post, money, options)
add_clerk(post, options)
add_transaction(post, options)
- add_card(action, post, get_card_token(authorization), options)
+ card_token = payment_method.is_a?(CreditCard) ? get_card_token(payment_method) : payment_method
+ add_card(action, post, card_token, options)
add_card_present(post, options)
commit(action, post, options)
end
+ alias credit refund
+
def void(authorization, options = {})
options[:invoice] = get_invoice(authorization)
commit('invoice', {}, options)
diff --git a/test/remote/gateways/remote_shift4_test.rb b/test/remote/gateways/remote_shift4_test.rb
index 0b4f3a0ef52..4403868aa71 100644
--- a/test/remote/gateways/remote_shift4_test.rb
+++ b/test/remote/gateways/remote_shift4_test.rb
@@ -215,6 +215,18 @@ def test_failed_refund
assert_include response.message, 'record not posted'
end
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.message, 'Transaction successful'
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ assert_include response.message, 'Card type not recognized'
+ end
+
def test_successful_refund
res = @gateway.purchase(@amount, @credit_card, @options)
assert_success res
diff --git a/test/unit/gateways/shift4_test.rb b/test/unit/gateways/shift4_test.rb
index bf187782c3e..267de4254c9 100644
--- a/test/unit/gateways/shift4_test.rb
+++ b/test/unit/gateways/shift4_test.rb
@@ -223,6 +223,18 @@ def test_successful_refund
assert_equal response.message, 'Transaction successful'
end
+ def test_successful_credit
+ stub_comms do
+ @gateway.refund(@amount, @credit_card, @options.merge!(invoice: '4666309473', expiration_date: '1235'))
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['card']['present'], 'N'
+ assert_equal request['card']['expirationDate'], '0924'
+ assert_nil request['card']['entryMode']
+ assert_nil request['customer']
+ end.respond_with(successful_refund_response)
+ end
+
def test_successful_void
@gateway.expects(:ssl_request).returns(successful_void_response)
response = @gateway.void('123')
From 34a127959d49531ac8ec6a95c4b3d1001cb766a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=2E=20Oca=C3=B1a?=
Date: Tue, 13 Jun 2023 17:33:47 +0100
Subject: [PATCH 094/390] Release v1.130.0
---
CHANGELOG | 4 ++++
lib/active_merchant/version.rb | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0c7e5085f4b..8a678e83602 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
* CyberSource Rest: Fixing currency detection [heavyblade] #4777
@@ -13,12 +15,14 @@
* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
* Worldpay: Fix Google Pay [almalee24] #4774
* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
+* Borgun: support for GBP currency [naashton] #4789
* CyberSource: Enable auto void on r230 [aenand] #4794
* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
* Stripe & Stripe PI: Validate API Key [almalee24] #4801
* Add BIN for Maestro [jcreiff] #4799
* D_Local: Add save field on card object [yunnydang] #4805
* PayPal Express: Adds support for MsgSubID property on DoReferenceTransaction and DoExpressCheckoutPayment [wikiti] #4798
+* Checkout_v2: use credit_card?, not case equality with CreditCard [bbraschi] #4803
* Shift4: Enable general credit feature [jherreraa] #4790
== Version 1.129.0 (May 3rd, 2023)
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index c51365b650a..aa54e52ddea 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.129.0'
+ VERSION = '1.130.0'
end
From 87ee55176155d64e691b754638cd7e7b4b89bc85 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Tue, 13 Jun 2023 16:23:46 -0400
Subject: [PATCH 095/390] Redsys: Add supported countries
This updates the list of countries that Redsys supports by adding France (FR), Great Britain (GB), Italy (IT), Poland (PL), and Portugal (PT)
CER-643
5527 tests, 77497 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/redsys.rb | 2 +-
test/unit/gateways/redsys_sha256_test.rb | 2 +-
test/unit/gateways/redsys_test.rb | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8a678e83602..e5829aebfac 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Redsys: Add supported countries [jcreiff] #4811
== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb
index 80217a94247..260c4d7d83f 100644
--- a/lib/active_merchant/billing/gateways/redsys.rb
+++ b/lib/active_merchant/billing/gateways/redsys.rb
@@ -39,7 +39,7 @@ class RedsysGateway < Gateway
self.live_url = 'https://sis.redsys.es/sis/operaciones'
self.test_url = 'https://sis-t.redsys.es:25443/sis/operaciones'
- self.supported_countries = ['ES']
+ self.supported_countries = %w[ES FR GB IT PL PT]
self.default_currency = 'EUR'
self.money_format = :cents
diff --git a/test/unit/gateways/redsys_sha256_test.rb b/test/unit/gateways/redsys_sha256_test.rb
index 1bada5830d4..66dae34a2c0 100644
--- a/test/unit/gateways/redsys_sha256_test.rb
+++ b/test/unit/gateways/redsys_sha256_test.rb
@@ -391,7 +391,7 @@ def test_default_currency
end
def test_supported_countries
- assert_equal ['ES'], RedsysGateway.supported_countries
+ assert_equal %w[ES FR GB IT PL PT], RedsysGateway.supported_countries
end
def test_supported_cardtypes
diff --git a/test/unit/gateways/redsys_test.rb b/test/unit/gateways/redsys_test.rb
index 8351babe5c1..605d077e38a 100644
--- a/test/unit/gateways/redsys_test.rb
+++ b/test/unit/gateways/redsys_test.rb
@@ -346,7 +346,7 @@ def test_default_currency
end
def test_supported_countries
- assert_equal ['ES'], RedsysGateway.supported_countries
+ assert_equal %w[ES FR GB IT PL PT], RedsysGateway.supported_countries
end
def test_supported_cardtypes
From 7fbffa0e206803ffe378ea2f819dfe3138f95c38 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Mon, 12 Jun 2023 10:04:46 -0400
Subject: [PATCH 096/390] Authorize.net: truncate nameOnAccount for bank
refunds
The API specification requires that the string be no longer than 22 characters; refunds will fail if this limit is exceeded
CER-670
REMOTE
85 tests, 304 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
UNIT
122 tests, 688 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
LOCAL
5525 tests, 77482 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
.../billing/gateways/authorize_net.rb | 2 +-
test/remote/gateways/remote_authorize_net_test.rb | 12 ++++++++++++
test/unit/gateways/authorize_net_test.rb | 14 ++++++++++++++
4 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index e5829aebfac..d46baefbd15 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
== HEAD
* Redsys: Add supported countries [jcreiff] #4811
+* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb
index 9f14cd69783..2016756aaac 100644
--- a/lib/active_merchant/billing/gateways/authorize_net.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net.rb
@@ -362,7 +362,7 @@ def normal_refund(amount, authorization, options)
xml.accountType(options[:account_type])
xml.routingNumber(options[:routing_number])
xml.accountNumber(options[:account_number])
- xml.nameOnAccount("#{options[:first_name]} #{options[:last_name]}")
+ xml.nameOnAccount(truncate("#{options[:first_name]} #{options[:last_name]}", 22))
end
else
xml.creditCard do
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index 4a1237b1c14..96117d0d685 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -835,6 +835,18 @@ def test_successful_echeck_refund
assert_match %r{The transaction cannot be found}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
end
+ def test_successful_echeck_refund_truncates_long_account_name
+ check_with_long_name = check(name: 'Michelangelo Donatello-Raphael')
+ purchase = @gateway.purchase(@amount, check_with_long_name, @options)
+ assert_success purchase
+
+ @options.update(first_name: check_with_long_name.first_name, last_name: check_with_long_name.last_name, routing_number: check_with_long_name.routing_number,
+ account_number: check_with_long_name.account_number, account_type: check_with_long_name.account_type)
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_failure refund
+ assert_match %r{The transaction cannot be found}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
def test_failed_credit
response = @gateway.credit(@amount, @declined_card, @options)
assert_failure response
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index 37f0d710f34..343179b37fd 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -1128,6 +1128,20 @@ def test_successful_bank_refund
assert_success response
end
+ def test_successful_bank_refund_truncates_long_name
+ response = stub_comms do
+ @gateway.refund(50, '12345667', account_type: 'checking', routing_number: '123450987', account_number: '12345667', first_name: 'Louise', last_name: 'Belcher-Williamson')
+ end.check_request do |_endpoint, data, _headers|
+ parse(data) do |doc|
+ assert_equal 'checking', doc.at_xpath('//transactionRequest/payment/bankAccount/accountType').content
+ assert_equal '123450987', doc.at_xpath('//transactionRequest/payment/bankAccount/routingNumber').content
+ assert_equal '12345667', doc.at_xpath('//transactionRequest/payment/bankAccount/accountNumber').content
+ assert_equal 'Louise Belcher-William', doc.at_xpath('//transactionRequest/payment/bankAccount/nameOnAccount').content
+ end
+ end.respond_with(successful_refund_response)
+ assert_success response
+ end
+
def test_refund_passing_extra_info
response = stub_comms do
@gateway.refund(50, '123456789', card_number: @credit_card.number, first_name: 'Bob', last_name: 'Smith', zip: '12345', order_id: '1', description: 'Refund for order 1')
From 66ba7524be215ae22b467154961b589bc1e0e906 Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Sat, 3 Jun 2023 01:54:43 -0400
Subject: [PATCH 097/390] Checkout: Add support for several customer data
fields
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CER-595
Ran into a lot of tricky data collisions and had to do some finagling to make sure existing test cases would pass. I left open two possibilities for passing in the phone number depending on how users would like to pass it in: manually via options or via the phone number that’s attached to credit card billing data on the payment method.
It’s possible to pay with a stored payment method which would be a `String`. In this case there’s no name data attached so added some guarding against NoMethodErrors that were resulting from trying to call payment_method.name.
Remote Tests:
92 tests, 221 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96.7391% passed
Unit Tests:
57 tests, 319 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Local Tests:
5516 tests, 77434 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.../billing/gateways/checkout_v2.rb | 10 ++++++
.../gateways/remote_checkout_v2_test.rb | 36 ++++++++++++++-----
test/unit/gateways/checkout_v2_test.rb | 35 ++++++++++++++++--
3 files changed, 70 insertions(+), 11 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 59c68e8272f..8cceb853869 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -139,6 +139,7 @@ def build_auth_or_purchase(post, amount, payment_method, options)
add_authorization_type(post, options)
add_payment_method(post, payment_method, options)
add_customer_data(post, options)
+ add_extra_customer_data(post, payment_method, options)
add_shipping_address(post, options)
add_stored_credential_options(post, options)
add_transaction_data(post, options)
@@ -239,6 +240,15 @@ def add_customer_data(post, options)
end
end
+ # created a separate method for these fields because they should not be included
+ # in all transaction types that include methods with source and customer fields
+ def add_extra_customer_data(post, payment_method, options)
+ post[:source][:phone] = {}
+ post[:source][:phone][:number] = options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ post[:source][:phone][:country_code] = options[:phone_country_code] if options[:phone_country_code]
+ post[:customer][:name] = payment_method.name if payment_method.respond_to?(:name)
+ end
+
def add_shipping_address(post, options)
if address = options[:shipping_address]
post[:shipping] = {}
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index fbc1fddeb78..d03e48b4748 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -100,6 +100,10 @@ def setup
authentication_response_status: 'Y'
}
)
+ @extra_customer_data = @options.merge(
+ phone_country_code: '1',
+ phone: '9108675309'
+ )
end
def test_transcript_scrubbing
@@ -312,8 +316,8 @@ def test_successful_purchase_includes_avs_result
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'G', response.avs_result['code']
- assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message']
+ assert_equal 'S', response.avs_result['code']
+ assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
end
def test_successful_purchase_includes_avs_result_via_oauth
@@ -328,8 +332,8 @@ def test_successful_authorize_includes_avs_result
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'G', response.avs_result['code']
- assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message']
+ assert_equal 'S', response.avs_result['code']
+ assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
end
def test_successful_purchase_includes_cvv_result
@@ -339,6 +343,12 @@ def test_successful_purchase_includes_cvv_result
assert_equal 'Y', response.cvv_result['code']
end
+ def test_successful_purchase_with_extra_customer_data
+ response = @gateway.purchase(@amount, @credit_card, @extra_customer_data)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_authorize_includes_cvv_result
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
@@ -432,7 +442,15 @@ def test_successful_purchase_with_shipping_address
end
def test_successful_purchase_without_phone_number
- response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: ''))
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: nil))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_name
+ credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: Time.now.year + 1, first_name: nil, last_name: nil)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_equal response.params['source']['name'], ''
assert_success response
assert_equal 'Succeeded', response.message
end
@@ -446,7 +464,7 @@ def test_successful_purchase_with_ip
def test_failed_purchase
response = @gateway.purchase(100, @credit_card_dnh, @options)
assert_failure response
- assert_equal 'Declined - Do Not Honour', response.message
+ assert_equal 'Invalid Card Number', response.message
end
def test_failed_purchase_via_oauth
@@ -470,7 +488,7 @@ def test_avs_failed_authorize
def test_invalid_shipping_address
response = @gateway.authorize(@amount, @credit_card, shipping_address: address.update(country: 'Canada'))
assert_failure response
- assert_equal 'request_invalid: address_country_invalid', response.message
+ assert_equal 'request_invalid: country_address_invalid', response.message
end
def test_successful_authorize_and_capture
@@ -827,8 +845,8 @@ def test_failed_verify
def test_expired_card_returns_error_code
response = @gateway.purchase(@amount, @expired_card, @options)
assert_failure response
- assert_equal 'processing_error: card_expired', response.message
- assert_equal 'processing_error: card_expired', response.error_code
+ assert_equal 'request_invalid: card_expired', response.message
+ assert_equal 'request_invalid: card_expired', response.error_code
end
def test_successful_purchase_with_idempotency_key
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index f0f59a7d6f7..7256db54e3f 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -24,6 +24,7 @@ def setup
})
@credit_card = credit_card
@amount = 100
+ @token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
def test_successful_purchase
@@ -413,6 +414,36 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_extra_customer_data
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ phone_country_code: '1',
+ billing_address: address
+ }
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['source']['phone']['number'], '(555)555-5555'
+ assert_equal request['source']['phone']['country_code'], '1'
+ assert_equal request['customer']['name'], 'Longbob Longsen'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_no_customer_name_included_in_token_purchase
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ phone_country_code: '1',
+ billing_address: address
+ }
+ @gateway.purchase(@amount, @token, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['source']['phone']['number'], '(555)555-5555'
+ assert_equal request['source']['phone']['country_code'], '1'
+ refute_includes data, 'name'
+ end.respond_with(successful_purchase_response)
+ end
+
def test_successful_purchase_with_metadata
response = stub_comms(@gateway, :ssl_request) do
options = {
@@ -810,7 +841,7 @@ def test_purchase_supports_alternate_credit_card_implementation
alternate_credit_card = alternate_credit_card_class.new
alternate_credit_card.expects(:credit_card?).returns(true)
- alternate_credit_card.expects(:name).returns(@credit_card.name)
+ alternate_credit_card.expects(:name).at_least_once.returns(@credit_card.name)
alternate_credit_card.expects(:number).returns(@credit_card.number)
alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
@@ -826,7 +857,7 @@ def test_authorize_supports_alternate_credit_card_implementation
alternate_credit_card = alternate_credit_card_class.new
alternate_credit_card.expects(:credit_card?).returns(true)
- alternate_credit_card.expects(:name).returns(@credit_card.name)
+ alternate_credit_card.expects(:name).at_least_once.returns(@credit_card.name)
alternate_credit_card.expects(:number).returns(@credit_card.number)
alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
From e72fe6650bfd9efd264a23d347aba6e9a2691cb7 Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Wed, 21 Jun 2023 06:29:03 -0400
Subject: [PATCH 098/390] Worldpay: check payment_method responds to
payment_cryptogram and eci (#4812)
---
CHANGELOG | 1 +
.../billing/gateways/worldpay.rb | 8 +++++-
test/unit/gateways/worldpay_test.rb | 28 +++++++++++++++++++
3 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index d46baefbd15..d42becf3b54 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* Redsys: Add supported countries [jcreiff] #4811
* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
+* Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index e25c6116d67..65c41a52371 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -997,12 +997,18 @@ def payment_details(payment_method)
when String
token_type_and_details(payment_method)
else
- type = payment_method.respond_to?(:source) ? :network_token : :credit
+ type = network_token?(payment_method) ? :network_token : :credit
{ payment_type: type }
end
end
+ def network_token?(payment_method)
+ payment_method.respond_to?(:source) &&
+ payment_method.respond_to?(:payment_cryptogram) &&
+ payment_method.respond_to?(:eci)
+ end
+
def token_type_and_details(token)
token_details = token_details_from_authorization(token)
token_details[:payment_type] = token_details.has_key?(:token_id) ? :token : :pay_as_order
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index b864eb5c228..0d98d10b89b 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -150,6 +150,34 @@ def test_payment_type_for_network_card
assert_equal payment, :network_token
end
+ def test_payment_type_returns_network_token_if_the_payment_method_responds_to_source_payment_cryptogram_and_eci
+ payment_method = mock
+ payment_method.stubs(source: nil, payment_cryptogram: nil, eci: nil)
+ result = @gateway.send(:payment_details, payment_method)
+ assert_equal({ payment_type: :network_token }, result)
+ end
+
+ def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_source
+ payment_method = mock
+ payment_method.stubs(payment_cryptogram: nil, eci: nil)
+ result = @gateway.send(:payment_details, payment_method)
+ assert_equal({ payment_type: :credit }, result)
+ end
+
+ def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_payment_cryptogram
+ payment_method = mock
+ payment_method.stubs(source: nil, eci: nil)
+ result = @gateway.send(:payment_details, payment_method)
+ assert_equal({ payment_type: :credit }, result)
+ end
+
+ def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_eci
+ payment_method = mock
+ payment_method.stubs(source: nil, payment_cryptogram: nil)
+ result = @gateway.send(:payment_details, payment_method)
+ assert_equal({ payment_type: :credit }, result)
+ end
+
def test_payment_type_for_credit_card
payment = @gateway.send(:payment_details, @credit_card)[:payment_type]
assert_equal payment, :credit
From c2ac2576618c0e9e95c9f0b5cf2c3c3c89dcc9b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=2E=20Oca=C3=B1a?=
Date: Wed, 21 Jun 2023 11:51:39 +0100
Subject: [PATCH 099/390] Release v1.131.0
---
CHANGELOG | 3 +++
lib/active_merchant/version.rb | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index d42becf3b54..d75e26fa4b4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,8 +2,11 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
+* CheckoutV2: Add support for several customer data fields [rachelkirk] #4800
* Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
== Version 1.130.0 (June 13th, 2023)
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index aa54e52ddea..424b00acbe0 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.130.0'
+ VERSION = '1.131.0'
end
From aeaa33cda7daff6843c425778677a73e51b2bc14 Mon Sep 17 00:00:00 2001
From: aenand
Date: Thu, 8 Jun 2023 12:59:15 -0400
Subject: [PATCH 100/390] Stripe PI: Add new stored credential flag
Stripe has a field called `stored_credential_transaction_type` to assist
merchants who vault outside of Stripe to recognize card on file transactions
at Stripe. This field does require Stripe enabling your account with this
field.
The standard stored credential fields map to the various possibilities that
Stripe makes available.
Test Summary
Remote:
87 tests, 409 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 58 ++++++++--
.../remote_stripe_payment_intents_test.rb | 71 ++++++++++++
.../gateways/stripe_payment_intents_test.rb | 102 ++++++++++++++++++
4 files changed, 226 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d75e26fa4b4..b932d3a0827 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 008726c08e8..475045da99f 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -34,9 +34,9 @@ def create_intent(money, payment_method, options = {})
add_connected_account(post, options)
add_radar_data(post, options)
add_shipping_address(post, options)
+ add_stored_credentials(post, options)
setup_future_usage(post, options)
add_exemption(post, options)
- add_stored_credentials(post, options)
add_ntid(post, options)
add_claim_without_transaction_id(post, options)
add_error_on_requires_action(post, options)
@@ -399,17 +399,19 @@ def add_exemption(post, options = {})
post[:payment_method_options][:card][:moto] = true if options[:moto]
end
- # Stripe Payment Intents does not pass any parameters for cardholder/merchant initiated
- # it also does not support installments for any country other than Mexico (reason for this is unknown)
- # The only thing that Stripe PI requires for stored credentials to work currently is the network_transaction_id
- # network_transaction_id is created when the card is authenticated using the field `setup_for_future_usage` with the value `off_session` see def setup_future_usage below
+ # Stripe Payment Intents now supports specifying on a transaction level basis stored credential information.
+ # The feature is currently gated but is listed as `stored_credential_transaction_type` inside the
+ # `post[:payment_method_options][:card]` hash. Since this is a beta field adding an extra check to use
+ # the existing logic by default. To be able to utilize this field, you must reach out to Stripe.
def add_stored_credentials(post, options = {})
return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
- stored_credential = options[:stored_credential]
post[:payment_method_options] ||= {}
post[:payment_method_options][:card] ||= {}
+ add_stored_credential_transaction_type(post, options) if options[:stored_credential_transaction_type]
+
+ stored_credential = options[:stored_credential]
post[:payment_method_options][:card][:mit_exemption] = {}
# Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
@@ -419,6 +421,50 @@ def add_stored_credentials(post, options = {})
post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
end
+ def add_stored_credential_transaction_type(post, options = {})
+ stored_credential = options[:stored_credential]
+ # Do not add anything unless these are present.
+ return unless stored_credential[:reason_type] && stored_credential[:initiator]
+
+ # Not compatible with off_session parameter.
+ options.delete(:off_session)
+ if stored_credential[:initial_transaction]
+ # Initial transactions must by CIT
+ return unless stored_credential[:initiator] == 'cardholder'
+
+ initial_transaction_stored_credential(post, stored_credential[:reason_type])
+ else
+ # Subsequent transaction
+ subsequent_transaction_stored_credential(post, stored_credential[:initiator], stored_credential[:reason_type])
+ end
+ end
+
+ def initial_transaction_stored_credential(post, reason_type)
+ if reason_type == 'unscheduled'
+ # Charge on-session and store card for future one-off payment use
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_unscheduled'
+ elsif reason_type == 'recurring'
+ # Charge on-session and store card for future recurring payment use
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_recurring'
+ else
+ # Charge on-session and store card for future on-session payment use.
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_on_session'
+ end
+ end
+
+ def subsequent_transaction_stored_credential(post, initiator, reason_type)
+ if initiator == 'cardholder'
+ # Charge on-session customer using previously stored card.
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_on_session'
+ elsif reason_type == 'recurring'
+ # Charge off-session customer using previously stored card for recurring transaction
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_recurring'
+ else
+ # Charge off-session customer using previously stored card for one-off transaction
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_unscheduled'
+ end
+ end
+
def add_ntid(post, options = {})
return unless options[:network_transaction_id]
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index b5c4a98d7ab..2ce122efeea 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -771,6 +771,77 @@ def test_succeeds_with_ntid_in_stored_credentials_and_separately
end
end
+ def test_succeeds_with_initial_cit
+ assert purchase = @gateway.purchase(@amount, @visa_card, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: true
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
+ def test_succeeds_with_initial_cit_3ds_required
+ assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required_setup_for_off_session, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: true
+ }
+ })
+ assert_success purchase
+ assert_equal 'requires_action', purchase.params['status']
+ end
+
+ def test_succeeds_with_mit
+ assert purchase = @gateway.purchase(@amount, @visa_card, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '1098510912210968'
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
+ def test_succeeds_with_mit_3ds_required
+ assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required_setup_for_off_session, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ initial_transaction: false,
+ network_transaction_id: '1098510912210968'
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
def test_successful_off_session_purchase_when_claim_without_transaction_id_present
[@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
assert response = @gateway.purchase(@amount, card_to_use, {
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 8cdb96381bf..1ea5fc5be2f 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -659,6 +659,108 @@ def test_scrub_filter_token
assert_equal @gateway.scrub(pre_scrubbed), scrubbed
end
+ def test_succesful_purchase_with_initial_cit_unscheduled
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'cardholder',
+ reason_type: 'unscheduled'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_off_session_unscheduled', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
+ def test_succesful_purchase_with_initial_cit_recurring
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'cardholder',
+ reason_type: 'recurring'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_off_session_recurring', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
+ def test_succesful_purchase_with_initial_cit_installment
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'cardholder',
+ reason_type: 'installment'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_on_session', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
+ def test_succesful_purchase_with_subsequent_cit
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'cardholder',
+ reason_type: 'installment'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_on_session', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
+ def test_succesful_purchase_with_mit_recurring
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'recurring'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_off_session_recurring', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
+ def test_succesful_purchase_with_mit_unscheduled
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'unscheduled'
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_off_session_unscheduled', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
private
def successful_setup_purchase
From 34c2caa610aa90a2fe3aba978ea3523ca502e469 Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Mon, 26 Jun 2023 08:17:35 -0500
Subject: [PATCH 101/390] Commerce Hub - Add a couple of GSFs (#4786)
Description
-------------------------
Add:
physical_goods_indicator maps to physicalGoodsIndicator inside of transactionDetails
scheme_reference_transaction_id maps to schemeReferenceTransactionId inside of storedCredentials
SER-501
Unit test
-------------------------
Finished in 33.616793 seconds.
5511 tests, 77405 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
163.94 tests/s, 2302.57 assertions/s
Rubocop
-------------------------
Inspecting 760 files
760 files inspected, no offenses detected
Co-authored-by: Luis
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
.../billing/gateways/commerce_hub.rb | 8 ++++++--
test/remote/gateways/remote_commerce_hub_test.rb | 14 +++++++++++---
test/unit/gateways/commerce_hub_test.rb | 15 +++++++++++++++
4 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index b932d3a0827..6353715eb82 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
== HEAD
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
+* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index a3b8eae3237..669b560445b 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -120,7 +120,11 @@ def add_transaction_interaction(post, options)
end
def add_transaction_details(post, options, action = nil)
- details = { captureFlag: options[:capture_flag], createToken: options[:create_token] }
+ details = {
+ captureFlag: options[:capture_flag],
+ createToken: options[:create_token],
+ physicalGoodsIndicator: [true, 'true'].include?(options[:physical_goods_indicator])
+ }
if options[:order_id].present? && action == 'sale'
details[:merchantOrderId] = options[:order_id]
@@ -214,7 +218,7 @@ def add_stored_credentials(post, options)
post[:storedCredentials][:sequence] = stored_credential[:initial_transaction] ? 'FIRST' : 'SUBSEQUENT'
post[:storedCredentials][:initiator] = stored_credential[:initiator] == 'merchant' ? 'MERCHANT' : 'CARD_HOLDER'
post[:storedCredentials][:scheduled] = SCHEDULED_REASON_TYPES.include?(stored_credential[:reason_type])
- post[:storedCredentials][:schemeReferenceTransactionId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ post[:storedCredentials][:schemeReferenceTransactionId] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id]
end
def add_credit_card(source, payment, options)
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index 9375a73c0b2..3bd4f9c4750 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -42,6 +42,14 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_purchase_whit_physical_goods_indicator
+ @options[:physical_goods_indicator] = true
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert response.params['transactionDetails']['physicalGoodsIndicator']
+ end
+
def test_successful_purchase_with_gsf_mit
@options[:data_entry_source] = 'ELECTRONIC_PAYMENT_TERMINAL'
@options[:pos_entry_mode] = 'CONTACTLESS'
@@ -109,7 +117,7 @@ def test_successful_purchase_with_stored_credential_framework
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
assert_equal '104', response.error_code
end
@@ -131,7 +139,7 @@ def test_successful_authorize
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
end
def test_successful_authorize_and_void
@@ -235,7 +243,7 @@ def test_successful_purchase_with_apple_pay
def test_failed_purchase_with_declined_apple_pay
response = @gateway.purchase(@amount, @declined_apple_pay, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
end
def test_transcript_scrubbing
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index e0c4e0ad5ef..8b18b2d32de 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -231,6 +231,21 @@ def test_successful_purchase_mit_with_gsf
assert_success response
end
+ def test_successful_purchase_with_gsf_scheme_reference_transaction_id
+ @options = stored_credential_options(:cardholder, :unscheduled, :initial)
+ @options[:physical_goods_indicator] = true
+ @options[:scheme_reference_transaction_id] = '12345'
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['storedCredentials']['schemeReferenceTransactionId'], '12345'
+ assert_equal request['transactionDetails']['physicalGoodsIndicator'], true
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def stored_credential_options(*args, ntid: nil)
{
order_id: '#1001',
From 3aef0558d4f264d6d8cdc4863ec8f106dc575d1c Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 28 Jun 2023 11:49:11 -0700
Subject: [PATCH 102/390] Nuvei (formerly SafeCharge): Add customer details to
credit action
---
lib/active_merchant/billing/gateways/safe_charge.rb | 1 +
test/remote/gateways/remote_safe_charge_test.rb | 6 ++++++
test/unit/gateways/safe_charge_test.rb | 10 ++++++++++
3 files changed, 17 insertions(+)
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index 49554a31454..a317e12f64d 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -86,6 +86,7 @@ def credit(money, payment, options = {})
add_payment(post, payment, options)
add_transaction_data('Credit', post, money, options)
+ add_customer_details(post, payment, options)
post[:sg_CreditType] = 1
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 158e1464518..9fe16b8f38f 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -282,6 +282,12 @@ def test_successful_credit_with_extra_options
assert_equal 'Success', response.message
end
+ def test_successful_credit_with_customer_details
+ response = @gateway.credit(@amount, credit_card('4444436501403986'), @options.merge(email: 'test@example.com'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
def test_failed_credit
response = @gateway.credit(@amount, @declined_card, @options)
assert_failure response
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index 2fdc49f11da..3cf3852a5b7 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -219,6 +219,16 @@ def test_successful_credit
assert_equal 'Success', response.message
end
+ def test_credit_sends_addtional_info
+ stub_comms do
+ @gateway.credit(@amount, @credit_card, @options.merge(email: 'test@example.com'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/sg_FirstName=Longbob/, data)
+ assert_match(/sg_LastName=Longsen/, data)
+ assert_match(/sg_Email/, data)
+ end.respond_with(successful_credit_response)
+ end
+
def test_failed_credit
@gateway.expects(:ssl_post).returns(failed_credit_response)
From 997d88e31c85d538b987127354a9f36c418abd7b Mon Sep 17 00:00:00 2001
From: David Perry
Date: Fri, 30 Jun 2023 11:44:18 -0400
Subject: [PATCH 103/390] IPG: Update live url to correct endpoint
The live_url for IPG was likely always incorrect, this updates it to hit
the actual endpoint.
Also changes test data to prevent a scrub test failure.
Remote (same failures on master):
18 tests, 40 assertions, 8 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
Unit:
27 tests, 123 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ipg.rb | 2 +-
test/remote/gateways/remote_ipg_test.rb | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 6353715eb82..8b000a15c4c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@
* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
* CheckoutV2: Add support for several customer data fields [rachelkirk] #4800
* Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
+* IPG: Update live url to correct endpoint [curiousepic] #4121
== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
diff --git a/lib/active_merchant/billing/gateways/ipg.rb b/lib/active_merchant/billing/gateways/ipg.rb
index 128f9e18a1d..139bfdec1c0 100644
--- a/lib/active_merchant/billing/gateways/ipg.rb
+++ b/lib/active_merchant/billing/gateways/ipg.rb
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class IpgGateway < Gateway
self.test_url = 'https://test.ipg-online.com/ipgapi/services'
- self.live_url = 'https://www5.ipg-online.com'
+ self.live_url = 'https://www5.ipg-online.com/ipgapi/services'
self.supported_countries = %w(AR)
self.default_currency = 'ARS'
diff --git a/test/remote/gateways/remote_ipg_test.rb b/test/remote/gateways/remote_ipg_test.rb
index 6b336d47780..9a81291b0c5 100644
--- a/test/remote/gateways/remote_ipg_test.rb
+++ b/test/remote/gateways/remote_ipg_test.rb
@@ -5,7 +5,7 @@ def setup
@gateway = IpgGateway.new(fixtures(:ipg))
@amount = 100
- @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '123', month: '12', year: '2029')
+ @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '987', month: '12', year: '2029')
@declined_card = credit_card('4000300011112220', brand: 'mastercard', verification_value: '652', month: '12', year: '2022')
@visa_card = credit_card('4704550000000005', brand: 'visa', verification_value: '123', month: '12', year: '2029')
@options = {
From 70d02bb2c6176a2ecebb80e3afe417cef3b37482 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 5 Jul 2023 12:19:14 -0500
Subject: [PATCH 104/390] vPos: Adding Panal Credit Card type (#4814)
Summary:
------------------------------
Add the panal credit card type and enables it for
vPos gateway
Unit Tests:
------------------------------
Finished in 38.864306 seconds.
5542 tests, 77546 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
760 files inspected, no offenses detectednitial commit
---
CHANGELOG | 1 +
lib/active_merchant/billing/credit_card.rb | 2 ++
lib/active_merchant/billing/credit_card_methods.rb | 8 ++++++--
lib/active_merchant/billing/gateways/vpos.rb | 2 +-
test/unit/credit_card_methods_test.rb | 4 ++++
5 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8b000a15c4c..ad7772ff7bb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
+* VPos: Adding Panal Credit Card type [jherreraa] #4814
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb
index 32460e7785e..8ad539c8a02 100644
--- a/lib/active_merchant/billing/credit_card.rb
+++ b/lib/active_merchant/billing/credit_card.rb
@@ -38,6 +38,7 @@ module Billing #:nodoc:
# * Edenred
# * Anda
# * Creditos directos (Tarjeta D)
+ # * Panal
#
# For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
# validations, allowing you to focus on your core concerns until you're ready to be more concerned
@@ -130,6 +131,7 @@ def number=(value)
# * +'edenred'+
# * +'anda'+
# * +'tarjeta-d'+
+ # * +'panal'+
#
# Or, if you wish to test your implementation, +'bogus'+.
#
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 6d32a6a01d7..4f11160c7fc 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -46,7 +46,8 @@ module CreditCardMethods
'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
'anda' => ->(num) { num =~ /^603199\d{10}$/ },
'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
- 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) }
+ 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
+ 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) }
}
SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -182,7 +183,8 @@ module CreditCardMethods
(601256..601276),
(601640..601652),
(601689..601700),
- (602011..602050),
+ (602011..602048),
+ [602050],
(630400..630499),
(639000..639099),
(670000..679999),
@@ -247,6 +249,8 @@ module CreditCardMethods
637568..637568, 637599..637599, 637609..637609, 637612..637612
]
+ PANAL_RANGES = [[602049]]
+
def self.included(base)
base.extend(ClassMethods)
end
diff --git a/lib/active_merchant/billing/gateways/vpos.rb b/lib/active_merchant/billing/gateways/vpos.rb
index be259e3b7ca..982b1391345 100644
--- a/lib/active_merchant/billing/gateways/vpos.rb
+++ b/lib/active_merchant/billing/gateways/vpos.rb
@@ -9,7 +9,7 @@ class VposGateway < Gateway
self.supported_countries = ['PY']
self.default_currency = 'PYG'
- self.supported_cardtypes = %i[visa master]
+ self.supported_cardtypes = %i[visa master panal]
self.homepage_url = 'https://comercios.bancard.com.py'
self.display_name = 'vPOS'
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index b4cf9b49c1a..ce8447abc11 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -502,6 +502,10 @@ def test_electron_cards
assert_false electron_test.call('42496200000000000')
end
+ def test_should_detect_panal_card
+ assert_equal 'panal', CreditCard.brand?('6020490000000000')
+ end
+
def test_credit_card?
assert credit_card.credit_card?
end
From 3c34a61acfb59c8a9b2478e718b6c918acdc7290 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 16 May 2023 15:10:07 -0500
Subject: [PATCH 105/390] Stripe PI: Update parameters for creation of customer
Start sending address, shipping, phone and email when creating
a customer.
Remote Tests
84 tests, 396 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests
42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 121 +++++++++---------
.../remote_stripe_payment_intents_test.rb | 20 ++-
.../gateways/stripe_payment_intents_test.rb | 15 ++-
4 files changed, 88 insertions(+), 69 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index ad7772ff7bb..fff480c5e26 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
* VPos: Adding Panal Credit Card type [jherreraa] #4814
+* Stripe PI: Update parameters for creation of customer [almalee24] #4782
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 475045da99f..b0e4f864741 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -76,22 +76,27 @@ def confirm_intent(intent_id, payment_method, options = {})
def create_payment_method(payment_method, options = {})
post_data = add_payment_method_data(payment_method, options)
-
options = format_idempotency_key(options, 'pm')
commit(:post, 'payment_methods', post_data, options)
end
def add_payment_method_data(payment_method, options = {})
- post_data = {}
- post_data[:type] = 'card'
- post_data[:card] = {}
- post_data[:card][:number] = payment_method.number
- post_data[:card][:exp_month] = payment_method.month
- post_data[:card][:exp_year] = payment_method.year
- post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
- add_billing_address(post_data, options)
- add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
- post_data
+ post = {
+ type: 'card',
+ card: {
+ number: payment_method.number,
+ exp_month: payment_method.month,
+ exp_year: payment_method.year
+ }
+ }
+
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
+ if billing = options[:billing_address] || options[:address]
+ post[:billing_details] = add_address(billing, options)
+ end
+
+ add_name_only(post, payment_method) if post[:billing_details].nil?
+ post
end
def add_payment_method_card_data_token(post_data, payment_method)
@@ -212,16 +217,7 @@ def store(payment_method, options = {})
result = add_payment_method_token(params, payment_method, options)
return result if result.is_a?(ActiveMerchant::Billing::Response)
- if options[:customer]
- customer_id = options[:customer]
- else
- post[:description] = options[:description] if options[:description]
- post[:email] = options[:email] if options[:email]
- options = format_idempotency_key(options, 'customer')
- post[:expand] = [:sources]
- customer = commit(:post, 'customers', post, options)
- customer_id = customer.params['id']
- end
+ customer_id = options[:customer] || customer(post, payment_method, options).params['id']
options = format_idempotency_key(options, 'attach')
attach_parameters = { customer: customer_id }
attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
@@ -231,6 +227,23 @@ def store(payment_method, options = {})
end
end
+ def customer(post, payment, options)
+ post[:description] = options[:description] if options[:description]
+ post[:expand] = [:sources]
+ post[:email] = options[:email]
+
+ if billing = options[:billing_address] || options[:address]
+ post.merge!(add_address(billing, options))
+ end
+
+ if shipping = options[:shipping_address]
+ post[:shipping] = add_address(shipping, options).except(:email)
+ end
+
+ options = format_idempotency_key(options, 'customer')
+ commit(:post, 'customers', post, options)
+ end
+
def unstore(identification, options = {}, deprecated_options = {})
if identification.include?('pm_')
_, payment_method = identification.split('|')
@@ -524,30 +537,35 @@ def setup_future_usage(post, options = {})
def add_billing_address_for_card_tokenization(post, options = {})
return unless (billing = options[:billing_address] || options[:address])
- post[:card][:address_city] = billing[:city] if billing[:city]
- post[:card][:address_country] = billing[:country] if billing[:country]
- post[:card][:address_line1] = billing[:address1] if billing[:address1]
- post[:card][:address_line2] = billing[:address2] if billing[:address2]
- post[:card][:address_zip] = billing[:zip] if billing[:zip]
- post[:card][:address_state] = billing[:state] if billing[:state]
+ billing = add_address(billing, options)
+ billing[:address].transform_keys! { |k| k == :postal_code ? :address_zip : k.to_s.prepend('address_').to_sym }
+
+ post[:card][:name] = billing[:name]
+ post[:card].merge!(billing[:address])
end
- def add_billing_address(post, options = {})
- return unless billing = options[:billing_address] || options[:address]
+ def add_shipping_address(post, options = {})
+ return unless shipping = options[:shipping_address]
- email = billing[:email] || options[:email]
+ post[:shipping] = add_address(shipping, options).except(:email)
+ post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
+ post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
+ end
- post[:billing_details] = {}
- post[:billing_details][:address] = {}
- post[:billing_details][:address][:city] = billing[:city] if billing[:city]
- post[:billing_details][:address][:country] = billing[:country] if billing[:country]
- post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
- post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
- post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
- post[:billing_details][:address][:state] = billing[:state] if billing[:state]
- post[:billing_details][:email] = email if email
- post[:billing_details][:name] = billing[:name] if billing[:name]
- post[:billing_details][:phone] = billing[:phone] if billing[:phone]
+ def add_address(address, options)
+ {
+ address: {
+ city: address[:city],
+ country: address[:country],
+ line1: address[:address1],
+ line2: address[:address2],
+ postal_code: address[:zip],
+ state: address[:state]
+ }.compact,
+ email: address[:email] || options[:email],
+ phone: address[:phone] || address[:phone_number],
+ name: address[:name]
+ }.compact
end
def add_name_only(post, payment_method)
@@ -557,27 +575,6 @@ def add_name_only(post, payment_method)
post[:billing_details][:name] = name
end
- def add_shipping_address(post, options = {})
- return unless shipping = options[:shipping_address]
-
- post[:shipping] = {}
-
- # fields required by Stripe PI
- post[:shipping][:address] = {}
- post[:shipping][:address][:line1] = shipping[:address1]
- post[:shipping][:name] = shipping[:name]
-
- # fields considered optional by Stripe PI
- post[:shipping][:address][:city] = shipping[:city] if shipping[:city]
- post[:shipping][:address][:country] = shipping[:country] if shipping[:country]
- post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2]
- post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip]
- post[:shipping][:address][:state] = shipping[:state] if shipping[:state]
- post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number]
- post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
- post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
- end
-
def format_idempotency_key(options, suffix)
return options unless options[:idempotency_key]
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 2ce122efeea..04399ed4f22 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -100,12 +100,15 @@ def test_successful_purchase_with_shipping_address
address1: 'block C',
address2: 'street 48',
zip: '22400',
- state: 'California'
+ state: 'California',
+ email: 'test@email.com'
}
}
+
assert response = @gateway.purchase(@amount, @visa_payment_method, options)
assert_success response
assert_equal 'succeeded', response.params['status']
+ assert_nil response.params['shipping']['email']
end
def test_successful_purchase_with_level3_data
@@ -1279,6 +1282,21 @@ def test_successful_store_with_idempotency_key
assert_equal store1.params['id'], store2.params['id']
end
+ def test_successful_customer_creating
+ options = {
+ currency: 'GBP',
+ billing_address: address,
+ shipping_address: address.merge!(email: 'test@email.com')
+ }
+ assert customer = @gateway.customer({}, @visa_card, options)
+
+ assert_equal customer.params['name'], 'Jim Smith'
+ assert_equal customer.params['phone'], '(555)555-5555'
+ assert_nil customer.params['shipping']['email']
+ assert_not_empty customer.params['shipping']
+ assert_not_empty customer.params['address']
+ end
+
def test_successful_store_with_false_validate_option
options = {
currency: 'GBP',
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 1ea5fc5be2f..ac03f9be645 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -31,9 +31,7 @@ def setup
brand: 'visa',
eci: '05',
month: '09',
- year: '2030',
- first_name: 'Longbob',
- last_name: 'Longsen'
+ year: '2030'
)
@apple_pay = network_tokenization_credit_card(
@@ -418,8 +416,11 @@ def test_purchase_with_google_pay_with_billing_address
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @google_pay, options)
end.check_request do |_method, endpoint, data, _headers|
- assert_match('card[tokenization_method]=android_pay', data) if %r{/tokens}.match?(endpoint)
- assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint)
+ if %r{/tokens}.match?(endpoint)
+ assert_match('card[name]=Jim+Smith', data)
+ assert_match('card[tokenization_method]=android_pay', data)
+ assert_match('card[address_line1]=456+My+Street', data)
+ end
assert_match('wallet[type]=google_pay', data) if %r{/wallet}.match?(endpoint)
assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint)
end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address)
@@ -437,7 +438,8 @@ def test_purchase_with_shipping_options
address1: 'block C',
address2: 'street 48',
zip: '22400',
- state: 'California'
+ state: 'California',
+ email: 'test@email.com'
}
}
stub_comms(@gateway, :ssl_request) do
@@ -451,6 +453,7 @@ def test_purchase_with_shipping_options
assert_match('shipping[address][state]=California', data)
assert_match('shipping[name]=John+Adam', data)
assert_match('shipping[phone]=%2B0018313818368', data)
+ assert_no_match(/shipping[email]/, data)
end.respond_with(successful_create_intent_response)
end
From 2566c8bc03cd9e40acbf0608b8e35958906960ff Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 1 Jun 2023 15:54:25 -0500
Subject: [PATCH 106/390] WorldPay: Update xml tag for Credit Cards
Update the xml tag for Credit Cards to be CARD-SSL
instead of being specific to card brand.
Remote:
100 tests, 416 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
109 tests, 637 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.../billing/gateways/worldpay.rb | 21 +------------------
test/unit/gateways/worldpay_test.rb | 16 +++++++-------
2 files changed, 9 insertions(+), 28 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index 65c41a52371..a531d59e8a0 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -28,21 +28,6 @@ class WorldpayGateway < Gateway
network_token: 'NETWORKTOKEN'
}
- CARD_CODES = {
- 'visa' => 'VISA-SSL',
- 'master' => 'ECMC-SSL',
- 'discover' => 'DISCOVER-SSL',
- 'american_express' => 'AMEX-SSL',
- 'jcb' => 'JCB-SSL',
- 'maestro' => 'MAESTRO-SSL',
- 'diners_club' => 'DINERS-SSL',
- 'elo' => 'ELO-SSL',
- 'naranja' => 'NARANJA-SSL',
- 'cabal' => 'CABAL-SSL',
- 'unionpay' => 'CHINAUNIONPAY-SSL',
- 'unknown' => 'CARD-SSL'
- }
-
AVS_CODE_MAP = {
'A' => 'M', # Match
'B' => 'P', # Postcode matches, address not verified
@@ -646,7 +631,7 @@ def add_token_details(xml, options)
end
def add_card_details(xml, payment_method, options)
- xml.tag! card_code_for(payment_method) do
+ xml.tag! 'CARD-SSL' do
add_card(xml, payment_method, options)
end
end
@@ -1034,10 +1019,6 @@ def currency_exponent(currency)
return 2
end
- def card_code_for(payment_method)
- CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown']
- end
-
def eligible_for_0_auth?(payment_method, options = {})
payment_method.is_a?(CreditCard) && %w(visa master).include?(payment_method.brand) && options[:zero_dollar_auth]
end
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index 0d98d10b89b..afb7b49bb57 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -201,7 +201,7 @@ def test_successful_authorize_without_name
end.check_request do |_endpoint, data, _headers|
assert_match(/4242424242424242/, data)
assert_no_match(/cardHolderName/, data)
- assert_match(/VISA-SSL/, data)
+ assert_match(/CARD-SSL/, data)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'R50704213207145707', response.authorization
@@ -1174,7 +1174,7 @@ def test_successful_store
assert_match %r(4242424242424242), data
assert_no_match %r(), data
assert_no_match %r(), data
- assert_no_match %r(), data
+ assert_no_match %r(), data
end.respond_with(successful_store_response)
assert_success response
@@ -2136,7 +2136,7 @@ def sample_authorization_request
Products Products Products
-
+
4242424242424242
@@ -2156,7 +2156,7 @@ def sample_authorization_request
(555)555-5555
-
+
@@ -2179,7 +2179,7 @@ def transcript
Purchase
-
+
4111111111111111
@@ -2195,7 +2195,7 @@ def transcript
US
-
+
wow@example.com
@@ -2214,7 +2214,7 @@ def scrubbed_transcript
Purchase
-
+
[FILTERED]
@@ -2230,7 +2230,7 @@ def scrubbed_transcript
US
-
+
wow@example.com
From efcd43d78e310f9b69cf04928d2c2d9eabbaecf6 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Mon, 3 Jul 2023 14:00:26 -0400
Subject: [PATCH 107/390] PaywayDotCom: Update live url
The gateway has advised to direct traffic to their failover .net endpoint
LOCAL
5542 tests, 77550 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
UNIT
16 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
REMOTE
16 tests, 43 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/payway_dot_com.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index fff480c5e26..75e07027363 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@
* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
* VPos: Adding Panal Credit Card type [jherreraa] #4814
* Stripe PI: Update parameters for creation of customer [almalee24] #4782
+* PaywayDotCom: update `live_url` [jcreiff] #4824
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/payway_dot_com.rb b/lib/active_merchant/billing/gateways/payway_dot_com.rb
index 06f6d919360..2b9775a521b 100644
--- a/lib/active_merchant/billing/gateways/payway_dot_com.rb
+++ b/lib/active_merchant/billing/gateways/payway_dot_com.rb
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaywayDotComGateway < Gateway
self.test_url = 'https://paywaywsdev.com/PaywayWS/Payment/CreditCard'
- self.live_url = 'https://paywayws.com/PaywayWS/Payment/CreditCard'
+ self.live_url = 'https://paywayws.net/PaywayWS/Payment/CreditCard'
self.supported_countries = %w[US CA]
self.default_currency = 'USD'
From 0521d61cad0ba286d3a388ccd0d09130955dec19 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 26 Jun 2023 11:54:07 -0500
Subject: [PATCH 108/390] Stripe: Update login key validation
Remote:
Stripe PI
87 tests, 409 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Stripe
77 tests, 362 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
Stripe PI
51 tests, 252 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Stripe
146 tests, 769 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/stripe.rb | 14 +++++++++++++-
.../gateways/stripe_payment_intents_test.rb | 17 ++++++++++++++++-
3 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 75e07027363..bf1c86ab4cc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@
* VPos: Adding Panal Credit Card type [jherreraa] #4814
* Stripe PI: Update parameters for creation of customer [almalee24] #4782
* PaywayDotCom: update `live_url` [jcreiff] #4824
+* Stripe & Stripe PI: Update login key validation [almalee24] #4816
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index 325845ef7dc..c18942ba879 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -696,7 +696,7 @@ def api_request(method, endpoint, parameters = nil, options = {})
def commit(method, url, parameters = nil, options = {})
add_expand_parameters(parameters, options) if parameters
- return Response.new(false, 'Invalid API Key provided') if test? && !key(options).start_with?('sk_test')
+ return Response.new(false, 'Invalid API Key provided') unless key_valid?(options)
response = api_request(method, url, parameters, options)
response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
@@ -716,6 +716,18 @@ def commit(method, url, parameters = nil, options = {})
error_code: success ? nil : error_code_from(response))
end
+ def key_valid?(options)
+ return true unless test?
+
+ %w(sk rk).each do |k|
+ if key(options).start_with?(k)
+ return false unless key(options).start_with?("#{k}_test")
+ end
+ end
+
+ true
+ end
+
def authorization_from(success, url, method, response)
return response.fetch('error', {})['charge'] unless success
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index ac03f9be645..0ed90ee1b1e 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -257,13 +257,28 @@ def test_failed_payment_methods_post
assert_equal 'invalid_request_error', create.params.dig('error', 'type')
end
- def test_invalid_login_test_transaction
+ def test_invalid_test_login_for_sk_key
gateway = StripePaymentIntentsGateway.new(login: 'sk_live_3422')
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_match 'Invalid API Key provided', response.message
end
+ def test_invalid_test_login_for_rk_key
+ gateway = StripePaymentIntentsGateway.new(login: 'rk_live_3422')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid API Key provided', response.message
+ end
+
+ def test_successful_purchase
+ gateway = StripePaymentIntentsGateway.new(login: '3422e230423s')
+
+ stub_comms(gateway, :ssl_request) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_create_intent_response)
+ end
+
def test_failed_error_on_requires_action
@gateway.expects(:ssl_request).returns(failed_with_set_error_on_requires_action_response)
From c8b989e6854f0273b02c5b8a436fb90b4bf2f5b4 Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 3 Jul 2023 11:46:49 -0400
Subject: [PATCH 109/390] CheckoutV2: Parse AVS and CVV checks
The CheckoutV2 gateway had logic in the response parsing
method that would limit the scope of parsing to only authorize or
purchase. This presents an issue for merchants using `verify-payment`
or other methods that may have the AVS and CVV checks in the response.
This commit also updates the AVS and CVV checks to use `dig` to
safely try parsing out the values
Test Summary
Remote:
93 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/checkout_v2.rb | 11 ++++-------
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index bf1c86ab4cc..c2c851595b5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@
* Stripe PI: Update parameters for creation of customer [almalee24] #4782
* PaywayDotCom: update `live_url` [jcreiff] #4824
* Stripe & Stripe PI: Update login key validation [almalee24] #4816
+* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 8cceb853869..13f6f7e757f 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -363,9 +363,6 @@ def commit(action, post, options, authorization = nil, method = :post)
end
def response(action, succeeded, response, source_id = nil)
- successful_response = succeeded && action == :purchase || action == :authorize
- avs_result = successful_response ? avs_result(response) : nil
- cvv_result = successful_response ? cvv_result(response) : nil
authorization = authorization_from(response) unless action == :unstore
body = action == :unstore ? { response_code: response.to_s } : response
Response.new(
@@ -375,8 +372,8 @@ def response(action, succeeded, response, source_id = nil)
authorization: authorization,
error_code: error_code_from(succeeded, body),
test: test?,
- avs_result: avs_result,
- cvv_result: cvv_result
+ avs_result: avs_result(response),
+ cvv_result: cvv_result(response)
)
end
@@ -427,11 +424,11 @@ def base_url
end
def avs_result(response)
- response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil
+ response.respond_to?(:dig) && response.dig('source', 'avs_check') ? AVSResult.new(code: response['source']['avs_check']) : nil
end
def cvv_result(response)
- response['source'] && response['source']['cvv_check'] ? CVVResult.new(response['source']['cvv_check']) : nil
+ response.respond_to?(:dig) && response.dig('source', 'cvv_check') ? CVVResult.new(response['source']['cvv_check']) : nil
end
def parse(body, error: nil)
From 5c9bd17ebf4cc7f6cbf9f65340619b7eb0eed199 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 7 Jul 2023 14:46:51 -0400
Subject: [PATCH 110/390] NMI: Add shipping_firstname, shipping_lastname,
shipping_email, and surcharge fields
CER-666
CER-673
LOCAL
5547 tests, 77613 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
UNIT
56 tests, 454 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
REMOTE
51 tests, 184 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
94.1176% passed
*Test failures are related to Apple Pay and eCheck, and are also failing on master
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/nmi.rb | 5 ++
test/remote/gateways/remote_nmi_test.rb | 20 +++++++
test/unit/gateways/nmi_test.rb | 64 +++++++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index c2c851595b5..af2fff09f75 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
* PaywayDotCom: update `live_url` [jcreiff] #4824
* Stripe & Stripe PI: Update login key validation [almalee24] #4816
* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
+* NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb
index 0268ae4bb23..aee8fa754a7 100644
--- a/lib/active_merchant/billing/gateways/nmi.rb
+++ b/lib/active_merchant/billing/gateways/nmi.rb
@@ -149,6 +149,7 @@ def add_level3_fields(post, options)
def add_invoice(post, money, options)
post[:amount] = amount(money)
+ post[:surcharge] = options[:surcharge] if options[:surcharge]
post[:orderid] = options[:order_id]
post[:orderdescription] = options[:description]
post[:currency] = options[:currency] || currency(money)
@@ -232,6 +233,9 @@ def add_customer_data(post, options)
end
if (shipping_address = options[:shipping_address])
+ first_name, last_name = split_names(shipping_address[:name])
+ post[:shipping_firstname] = first_name if first_name
+ post[:shipping_lastname] = last_name if last_name
post[:shipping_company] = shipping_address[:company]
post[:shipping_address1] = shipping_address[:address1]
post[:shipping_address2] = shipping_address[:address2]
@@ -240,6 +244,7 @@ def add_customer_data(post, options)
post[:shipping_country] = shipping_address[:country]
post[:shipping_zip] = shipping_address[:zip]
post[:shipping_phone] = shipping_address[:phone]
+ post[:shipping_email] = options[:shipping_email] if options[:shipping_email]
end
if (descriptor = options[:descriptors])
diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb
index ad01c314b92..3ba0732241c 100644
--- a/test/remote/gateways/remote_nmi_test.rb
+++ b/test/remote/gateways/remote_nmi_test.rb
@@ -193,6 +193,26 @@ def test_successful_purchase_with_descriptors
assert response.authorization
end
+ def test_successful_purchase_with_shipping_fields
+ options = @options.merge({ shipping_address: shipping_address, shipping_email: 'test@example.com' })
+
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_surcharge
+ options = @options.merge({ surcharge: '1.00' })
+
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
def test_failed_authorization
assert response = @gateway.authorize(99, @credit_card, @options)
assert_failure response
diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb
index 7cbf9ef6510..160ea8a3f86 100644
--- a/test/unit/gateways/nmi_test.rb
+++ b/test/unit/gateways/nmi_test.rb
@@ -125,6 +125,70 @@ def test_purchase_with_options
assert_success response
end
+ def test_purchase_with_surcharge
+ options = @transaction_options.merge({ surcharge: '1.00' })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/surcharge=1.00/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_fields
+ options = @transaction_options.merge({ shipping_address: shipping_address })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/shipping_firstname=Jon/, data)
+ assert_match(/shipping_lastname=Smith/, data)
+ assert_match(/shipping_address1=123\+Your\+Street/, data)
+ assert_match(/shipping_address2=Apt\+2/, data)
+ assert_match(/shipping_city=Toronto/, data)
+ assert_match(/shipping_state=ON/, data)
+ assert_match(/shipping_country=CA/, data)
+ assert_match(/shipping_zip=K2C3N7/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_fields_omits_blank_name
+ options = @transaction_options.merge({ shipping_address: shipping_address(name: nil) })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ refute_match(/shipping_firstname/, data)
+ refute_match(/shipping_lastname/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_email
+ options = @transaction_options.merge({ shipping_address: shipping_address, shipping_email: 'test@example.com' })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/shipping_email=test%40example\.com/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_failed_purchase
response = stub_comms do
@gateway.purchase(@amount, @credit_card)
From b135d1843e5c93ec44c2dc3501f9d15c445dcec3 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 7 Jul 2023 16:58:20 -0500
Subject: [PATCH 111/390] Borgun: Update authorization_from & message_from
Update authorization_from to return nil if the transaction failed or
it is a 3DS transaction. Update message_from to return ErrorMessage
if present.
Unit:
12 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
22 tests, 43 assertions, 6 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
72.7273% passed
---
CHANGELOG | 1 +
.../billing/gateways/borgun.rb | 10 +++---
test/unit/gateways/borgun_test.rb | 34 +++++++++++++++++++
3 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index af2fff09f75..06daee6a15b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@
* Stripe & Stripe PI: Update login key validation [almalee24] #4816
* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
* NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
+* Borgun: Update authorization_from & message_from [almalee24] #4826
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
index 1850425e697..778c6bc64eb 100644
--- a/lib/active_merchant/billing/gateways/borgun.rb
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -172,7 +172,7 @@ def commit(action, post, options = {})
success,
message_from(success, pairs),
pairs,
- authorization: authorization_from(pairs),
+ authorization: authorization_from(pairs, options),
test: test?
)
end
@@ -185,12 +185,12 @@ def message_from(succeeded, response)
if succeeded
'Succeeded'
else
- response[:message] || "Error with ActionCode=#{response[:actioncode]}"
+ response[:message] || response[:status_errormessage] || "Error with ActionCode=#{response[:actioncode]}"
end
end
- def authorization_from(response)
- [
+ def authorization_from(response, options)
+ authorization = [
response[:dateandtime],
response[:batch],
response[:transaction],
@@ -200,6 +200,8 @@ def authorization_from(response)
response[:tramount],
response[:trcurrency]
].join('|')
+
+ authorization == '|||||||' ? nil : authorization
end
def split_authorization(authorization)
diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb
index 546d6aaa249..8550b24eb79 100644
--- a/test/unit/gateways/borgun_test.rb
+++ b/test/unit/gateways/borgun_test.rb
@@ -56,6 +56,20 @@ def test_authorize_and_capture
assert_success capture
end
+ def test_failed_preauth_3ds
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/MerchantReturnURL>#{@options[:redirect_url]}/, data)
+ assert_match(/SaleDescription>#{@options[:sale_description]}/, data)
+ assert_match(/TrCurrencyExponent>2/, data)
+ end.respond_with(failed_get_3ds_authentication_response)
+
+ assert_failure response
+ assert_equal response.message, 'Exception in PostEnrollmentRequest.'
+ assert response.authorization.blank?
+ end
+
def test_successful_preauth_3ds
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
@@ -70,6 +84,7 @@ def test_successful_preauth_3ds
assert !response.params['acsformfields_actionurl'].blank?
assert !response.params['acsformfields_pareq'].blank?
assert !response.params['threedsmessageid'].blank?
+ assert response.authorization.blank?
end
def test_successful_purchase_after_3ds
@@ -369,6 +384,25 @@ def successful_get_3ds_authentication_response
RESPONSE
end
+ def failed_get_3ds_authentication_response
+ %(
+
+
+
+
+ <?xml version="1.0" encoding="iso-8859-1"?>
+ <get3DSAuthenticationReply>
+ <Status>
+ <ResultCode>30</ResultCode>
+ <ResultText>MPI returns error</ResultText>
+ <ErrorMessage>Exception in PostEnrollmentRequest.</ErrorMessage>
+ </Status>
+ </get3DSAuthenticationReply>
+
+
+ )
+ end
+
def transcript
<<-PRE_SCRUBBED
<- "POST /ws/Heimir.pub.ws:Authorization HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic yyyyyyyyyyyyyyyyyyyyyyyyyy==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway01.borgun.is\r\nContent-Length: 1220\r\n\r\n"
From 97f2037a8c7a67ddbed43db5e8995c1e6bdb98a4 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 11 Jul 2023 15:27:43 -0500
Subject: [PATCH 112/390] Kushki: Add Brazil as supported country
Unit
17 tests, 109 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote
18 tests, 57 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/kushki.rb | 2 +-
test/remote/gateways/remote_kushki_test.rb | 15 ++++++++++++++-
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 06daee6a15b..a4443415d26 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,7 @@
* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
* NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
* Borgun: Update authorization_from & message_from [almalee24] #4826
+* Kushki: Add Brazil as supported country [almalee24] #4829
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index c4101069450..9b3e726618c 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -7,7 +7,7 @@ class KushkiGateway < Gateway
self.test_url = 'https://api-uat.kushkipagos.com/'
self.live_url = 'https://api.kushkipagos.com/'
- self.supported_countries = %w[CL CO EC MX PE]
+ self.supported_countries = %w[BR CL CO EC MX PE]
self.default_currency = 'USD'
self.money_format = :dollars
self.supported_cardtypes = %i[visa master american_express discover diners_club alia]
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index 2e575a2f7ad..7b84626e4f0 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -15,6 +15,13 @@ def test_successful_purchase
assert_match %r(^\d+$), response.authorization
end
+ def test_successful_purchase_brazil
+ response = @gateway.purchase(@amount, @credit_card, { currency: 'BRL' })
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
def test_successful_purchase_with_options
options = {
currency: 'USD',
@@ -137,13 +144,19 @@ def test_failed_purchase
end
def test_successful_authorize
- # Kushki only allows preauthorization for PEN, CLP, and UF.
response = @gateway.authorize(@amount, @credit_card, { currency: 'PEN' })
assert_success response
assert_equal 'Succeeded', response.message
assert_match %r(^\d+$), response.authorization
end
+ def test_successful_authorize_brazil
+ response = @gateway.authorize(@amount, @credit_card, { currency: 'BRL' })
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
def test_approval_code_comes_back_when_passing_full_response
options = {
full_response: true
From fdac501814a0457812f127afa8be97deb691f73e Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Tue, 20 Jun 2023 16:49:30 -0500
Subject: [PATCH 113/390] Adyen: Add additional data for airline and lodging
Description
-------------------------
GWS-67
This commit adds additional data for Adyen in order
to be able to send information regarding the airline and lodging.
To send this new data, it sends fields additional_data_airline and
additional_data_lodging as a GSF.
Unit test
-------------------------
Finished in 0.157969 seconds.
109 tests, 567 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
690.01 tests/s, 3589.31 assertions/s
Remote test
-------------------------
Finished in 176.357086 seconds.
134 tests, 447 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.791% passed
0.76 tests/s, 2.53 assertions/s
Rubocop
-------------------------
760 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 80 ++++++++++++++++++
test/remote/gateways/remote_adyen_test.rb | 63 +++++++++++++-
test/unit/gateways/adyen_test.rb | 82 +++++++++++++++++++
4 files changed, 222 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a4443415d26..f828e1a7e39 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@
* NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
* Borgun: Update authorization_from & message_from [almalee24] #4826
* Kushki: Add Brazil as supported country [almalee24] #4829
+* Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 495afeb9dc4..eb06a185379 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -68,6 +68,8 @@ def authorize(money, payment, options = {})
add_application_info(post, options)
add_level_2_data(post, options)
add_level_3_data(post, options)
+ add_data_airline(post, options)
+ add_data_lodging(post, options)
commit('authorise', post, options)
end
@@ -291,6 +293,84 @@ def add_level_3_data(post, options)
post[:additionalData].compact!
end
+ def add_data_airline(post, options)
+ return unless options[:additional_data_airline]
+
+ mapper = %w[
+ agency_invoice_number
+ agency_plan_name
+ airline_code
+ airline_designator_code
+ boarding_fee
+ computerized_reservation_system
+ customer_reference_number
+ document_type
+ flight_date
+ ticket_issue_address
+ ticket_number
+ travel_agency_code
+ travel_agency_name
+ passenger_name
+ ].each_with_object({}) { |value, hash| hash["airline.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_airline]))
+
+ if options[:additional_data_airline][:leg].present?
+ leg_data = %w[
+ carrier_code
+ class_of_travel
+ date_of_travel
+ depart_airport
+ depart_tax
+ destination_code
+ fare_base_code
+ flight_number
+ stop_over_code
+ ].each_with_object({}) { |value, hash| hash["airline.leg.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(leg_data, options[:additional_data_airline][:leg]))
+ end
+
+ if options[:additional_data_airline][:passenger].present?
+ passenger_data = %w[
+ date_of_birth
+ first_name
+ last_name
+ telephone_number
+ traveller_type
+ ].each_with_object({}) { |value, hash| hash["airline.passenger.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(passenger_data, options[:additional_data_airline][:passenger]))
+ end
+ post[:additionalData].compact!
+ end
+
+ def add_data_lodging(post, options)
+ return unless options[:additional_data_lodging]
+
+ mapper = {
+ 'lodging.checkInDate': 'check_in_date',
+ 'lodging.checkOutDate': 'check_out_date',
+ 'lodging.customerServiceTollFreeNumber': 'customer_service_toll_free_number',
+ 'lodging.fireSafetyActIndicator': 'fire_safety_act_indicator',
+ 'lodging.folioCashAdvances': 'folio_cash_advances',
+ 'lodging.folioNumber': 'folio_number',
+ 'lodging.foodBeverageCharges': 'food_beverage_charges',
+ 'lodging.noShowIndicator': 'no_show_indicator',
+ 'lodging.prepaidExpenses': 'prepaid_expenses',
+ 'lodging.propertyPhoneNumber': 'property_phone_number',
+ 'lodging.room1.numberOfNights': 'number_of_nights',
+ 'lodging.room1.rate': 'rate',
+ 'lodging.totalRoomTax': 'total_room_tax',
+ 'lodging.totalTax': 'totalTax',
+ 'travelEntertainmentAuthData.duration': 'duration',
+ 'travelEntertainmentAuthData.market': 'market'
+ }
+
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_lodging]))
+ post[:additionalData].compact!
+ end
+
def add_shopper_data(post, options)
post[:shopperEmail] = options[:email] if options[:email]
post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index 83b43d71b05..7c7e139f7ed 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -1015,11 +1015,9 @@ def test_successful_store_with_elo_card
assert_equal 'Authorised', response.message
end
- # Adyen does not currently support recurring transactions with Cabal cards
- def test_failed_store_with_cabal_card
+ def test_successful_store_with_cabal_card
assert response = @gateway.store(@cabal_credit_card, @options)
- assert_failure response
- assert_equal 'Recurring transactions are not supported for this card type.', response.message
+ assert_success response
end
def test_successful_store_with_unionpay_card
@@ -1499,6 +1497,63 @@ def test_successful_purchase_with_level_3_data
assert_equal '[capture-received]', response.message
end
+ def test_succesful_purchase_with_airline_data
+ airline_data = {
+ agency_invoice_number: 'BAC123',
+ agency_plan_name: 'plan name',
+ airline_code: '434234',
+ airline_designator_code: '1234',
+ boarding_fee: '100',
+ computerized_reservation_system: 'abcd',
+ customer_reference_number: 'asdf1234',
+ document_type: 'cc',
+ flight_date: '2023-09-08',
+ ticket_issue_address: 'abcqwer',
+ ticket_number: 'ABCASDF',
+ travel_agency_code: 'ASDF',
+ travel_agency_name: 'hopper',
+ passenger_name: 'Joe Doe',
+ leg: {
+ carrier_code: 'KL',
+ class_of_travel: 'F'
+ },
+ passenger: {
+ first_name: 'Joe',
+ last_name: 'Doe',
+ telephone_number: '432211111'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_data_airline: airline_data))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_succesful_purchase_with_lodging_data
+ lodging_data = {
+ check_in_date: '20230822',
+ check_out_date: '20230830',
+ customer_service_toll_free_number: '234234',
+ fire_safety_act_indicator: 'abc123',
+ folio_cash_advances: '1234667',
+ folio_number: '32343',
+ food_beverage_charges: '1234',
+ no_show_indicator: 'Y',
+ prepaid_expenses: '100',
+ property_phone_number: '54545454',
+ number_of_nights: '5',
+ rate: '100',
+ total_room_tax: '1000',
+ total_tax: '100',
+ duration: '2',
+ market: 'H'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_data_lodging: lodging_data))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
def test_successful_cancel_or_refund
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 864bf7d166d..c06b8ed7c7e 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -1332,6 +1332,88 @@ def test_level_3_data
assert_success response
end
+ def test_succesful_additional_airline_data
+ airline_data = {
+ agency_invoice_number: 'BAC123',
+ agency_plan_name: 'plan name',
+ airline_code: '434234',
+ airline_designator_code: '1234',
+ boarding_fee: '100',
+ computerized_reservation_system: 'abcd',
+ customer_reference_number: 'asdf1234',
+ document_type: 'cc',
+ leg: {
+ carrier_code: 'KL'
+ },
+ passenger: {
+ first_name: 'Joe',
+ last_name: 'Doe'
+ }
+ }
+
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(additional_data_airline: airline_data))
+ end.check_request do |_endpoint, data, _headers|
+ parsed = JSON.parse(data)
+ additional_data = parsed['additionalData']
+ assert_equal additional_data['airline.agency_invoice_number'], airline_data[:agency_invoice_number]
+ assert_equal additional_data['airline.agency_plan_name'], airline_data[:agency_plan_name]
+ assert_equal additional_data['airline.airline_code'], airline_data[:airline_code]
+ assert_equal additional_data['airline.airline_designator_code'], airline_data[:airline_designator_code]
+ assert_equal additional_data['airline.boarding_fee'], airline_data[:boarding_fee]
+ assert_equal additional_data['airline.computerized_reservation_system'], airline_data[:computerized_reservation_system]
+ assert_equal additional_data['airline.customer_reference_number'], airline_data[:customer_reference_number]
+ assert_equal additional_data['airline.document_type'], airline_data[:document_type]
+ assert_equal additional_data['airline.flight_date'], airline_data[:flight_date]
+ assert_equal additional_data['airline.ticket_issue_address'], airline_data[:abcqwer]
+ assert_equal additional_data['airline.ticket_number'], airline_data[:ticket_number]
+ assert_equal additional_data['airline.travel_agency_code'], airline_data[:travel_agency_code]
+ assert_equal additional_data['airline.travel_agency_name'], airline_data[:travel_agency_name]
+ assert_equal additional_data['airline.passenger_name'], airline_data[:passenger_name]
+ assert_equal additional_data['airline.leg.carrier_code'], airline_data[:leg][:carrier_code]
+ assert_equal additional_data['airline.leg.class_of_travel'], airline_data[:leg][:class_of_travel]
+ assert_equal additional_data['airline.passenger.first_name'], airline_data[:passenger][:first_name]
+ assert_equal additional_data['airline.passenger.last_name'], airline_data[:passenger][:last_name]
+ assert_equal additional_data['airline.passenger.telephone_number'], airline_data[:passenger][:telephone_number]
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_additional_data_lodging
+ lodging_data = {
+ check_in_date: '20230822',
+ check_out_date: '20230830',
+ customer_service_toll_free_number: '234234',
+ fire_safety_act_indicator: 'abc123',
+ folio_cash_advances: '1234667',
+ folio_number: '32343',
+ food_beverage_charges: '1234',
+ no_show_indicator: 'Y',
+ prepaid_expenses: '100',
+ property_phone_number: '54545454',
+ number_of_nights: '5'
+ }
+
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(additional_data_lodging: lodging_data))
+ end.check_request do |_endpoint, data, _headers|
+ parsed = JSON.parse(data)
+ additional_data = parsed['additionalData']
+ assert_equal additional_data['lodging.checkInDate'], lodging_data[:check_in_date]
+ assert_equal additional_data['lodging.checkOutDate'], lodging_data[:check_out_date]
+ assert_equal additional_data['lodging.customerServiceTollFreeNumber'], lodging_data[:customer_service_toll_free_number]
+ assert_equal additional_data['lodging.fireSafetyActIndicator'], lodging_data[:fire_safety_act_indicator]
+ assert_equal additional_data['lodging.folioCashAdvances'], lodging_data[:folio_cash_advances]
+ assert_equal additional_data['lodging.folioNumber'], lodging_data[:folio_number]
+ assert_equal additional_data['lodging.foodBeverageCharges'], lodging_data[:food_beverage_charges]
+ assert_equal additional_data['lodging.noShowIndicator'], lodging_data[:no_show_indicator]
+ assert_equal additional_data['lodging.prepaidExpenses'], lodging_data[:prepaid_expenses]
+ assert_equal additional_data['lodging.propertyPhoneNumber'], lodging_data[:property_phone_number]
+ assert_equal additional_data['lodging.room1.numberOfNights'], lodging_data[:number_of_nights]
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_extended_avs_response
response = stub_comms do
@gateway.verify(@credit_card, @options)
From 8f663b6a96433c6a3604c2a7660e13cbbd3802cd Mon Sep 17 00:00:00 2001
From: Alejandro Flores
Date: Mon, 26 Dec 2022 10:25:38 -0600
Subject: [PATCH 114/390] MIT: Changed how the payload was sent to the gateway
Closes #4655
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/mit.rb | 36 ++---
test/unit/gateways/mit_test.rb | 168 +++++++++++---------
3 files changed, 109 insertions(+), 96 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f828e1a7e39..b544b50d3e5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,7 @@
* Borgun: Update authorization_from & message_from [almalee24] #4826
* Kushki: Add Brazil as supported country [almalee24] #4829
* Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
+* MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/mit.rb b/lib/active_merchant/billing/gateways/mit.rb
index 74b7bf6beab..785be9368da 100644
--- a/lib/active_merchant/billing/gateways/mit.rb
+++ b/lib/active_merchant/billing/gateways/mit.rb
@@ -93,8 +93,7 @@ def authorize(money, payment, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('sale', json_post)
end
@@ -114,8 +113,7 @@ def capture(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('capture', json_post)
end
@@ -136,8 +134,7 @@ def refund(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('refund', json_post)
end
@@ -145,10 +142,18 @@ def supports_scrubbing?
true
end
+ def extract_mit_responses_from_transcript(transcript)
+ groups = transcript.scan(/reading \d+ bytes(.*?)read \d+ bytes/m)
+ groups.map do |group|
+ group.first.scan(/-> "(.*?)"/).flatten.map(&:strip).join('')
+ end
+ end
+
def scrub(transcript)
ret_transcript = transcript
auth_origin = ret_transcript[/(.*?)<\/authorization>/, 1]
unless auth_origin.nil?
+ auth_origin = auth_origin.gsub('\n', '')
auth_decrypted = decrypt(auth_origin, @options[:key_session])
auth_json = JSON.parse(auth_decrypted)
auth_json['card'] = '[FILTERED]'
@@ -162,6 +167,7 @@ def scrub(transcript)
cap_origin = ret_transcript[/(.*?)<\/capture>/, 1]
unless cap_origin.nil?
+ cap_origin = cap_origin.gsub('\n', '')
cap_decrypted = decrypt(cap_origin, @options[:key_session])
cap_json = JSON.parse(cap_decrypted)
cap_json['apikey'] = '[FILTERED]'
@@ -173,6 +179,7 @@ def scrub(transcript)
ref_origin = ret_transcript[/(.*?)<\/refund>/, 1]
unless ref_origin.nil?
+ ref_origin = ref_origin.gsub('\n', '')
ref_decrypted = decrypt(ref_origin, @options[:key_session])
ref_json = JSON.parse(ref_decrypted)
ref_json['apikey'] = '[FILTERED]'
@@ -182,15 +189,10 @@ def scrub(transcript)
ret_transcript = ret_transcript.gsub(/(.*?)<\/refund>/, ref_tagged)
end
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
- loop do
- break if res_origin.nil?
-
- resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
- resp_decrypted = decrypt(resp_origin, @options[:key_session])
- ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
- ret_transcript = ret_transcript.sub('reading ', 'response: ')
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
+ groups = extract_mit_responses_from_transcript(transcript)
+ groups.each do |group|
+ group_decrypted = decrypt(group, @options[:key_session])
+ ret_transcript = ret_transcript.gsub('Conn close', "\n" + group_decrypted + "\nConn close")
end
ret_transcript
@@ -219,9 +221,7 @@ def add_payment(post, payment)
end
def commit(action, parameters)
- json_str = JSON.generate(parameters)
- cleaned_str = json_str.gsub('\n', '')
- raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
+ raw_response = ssl_post(live_url, parameters, { 'Content-type' => 'text/plain' })
response = JSON.parse(decrypt(raw_response, @options[:key_session]))
Response.new(
diff --git a/test/unit/gateways/mit_test.rb b/test/unit/gateways/mit_test.rb
index 16a80355f4f..4ae7b4932be 100644
--- a/test/unit/gateways/mit_test.rb
+++ b/test/unit/gateways/mit_test.rb
@@ -169,88 +169,100 @@ def failed_void_response
end
def pre_scrubbed
- <<-PRE_SCRUBBED
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"1aUSihtRXgd+1nycRfVWgv0JDZsGLsrpsNkahpkx4jmnBRRAPPao+zJYqsN4xrGMIeVdJ3Y5LlQYXg5qu8O7iZmDPTqWbyKmsurCxJidr6AkFszwvRfugElyb5sAYpUcrnFSpVUgz2NGcIuMRalr0irf7q30+TzbLRHQc1Z5QTe6am3ndO8aSKKLwYYmfHcO8E/+dPiCsSP09P2heNqpMbf5IKdSwGCVS1Rtpcoijl3wXB8zgeBZ1PXHAmmkC1/CWRs/fh1qmvYFzb8YAiRy5q80Tyq09IaeSpQ1ydq3r95QBSJy6H4gz2OV/v2xdm1A63XEh2+6N6p2XDyzGWQrxKE41wmqRCxie7qY2xqdv4S8Cl8ldSMEpZY46A68hKIN6zrj6eMWxauwdi6ZkZfMDuh9Pn9x5gwwgfElLopIpR8fejB6G4hAQHtq2jhn5D4ccmAqNxkrB4w5k+zc53Rupk2u3MDp5T5sRkqvNyIN2kCE6i0DD9HlqkCjWV+bG9WcUiO4D7m5fWRE5f9OQ2XjeA==IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 320\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- reading 320 bytes...
- -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
- read 320 bytes
- Conn close
- opening connection to wpy.mitec.com.mx:443...
- opened
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"Z6l24tZG2YfTOQTne8NVygr/YeuVRNya8ZUCM5NvRgOEL/Mt8PO0voNnspoiFSg+RVamC4V2BipmU3spPVBg6Dr0xMpPL7ryVB9mlM4PokUdHkZTjXJHbbr1GWdyEPMYYSH0f+M1qUDO57EyUuZv8o6QSv+a/tuOrrBwsHI8cnsv+y9qt5L9LuGRMeBYvZkkK+xw53eDqYsJGoCvpk/pljCCkGU7Q/sKsLOx0MT6dA/BLVGrGeo8ngO+W/cnOigGfIZJSPFTcrUKI/Q7AsHuP+3lG6q9VAri9UJZXm5pWOg=IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 280\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- reading 280 bytes...
- -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
- read 280 bytes
- Conn close
+ <<~PRE_SCRUBBED
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
+ <- "{\"payload\":\"1aUSihtRXgd+1nycRfVWgv0JDZsGLsrpsNkahpkx4jmnBRRAPPao+zJYqsN4xrGMIeVdJ3Y5LlQYXg5qu8O7iZmDPTqWbyKmsurCxJidr6AkFszwvRfugElyb5sAYpUcrnFSpVUgz2NGcIuMRalr0irf7q30+TzbLRHQc1Z5QTe6am3ndO8aSKKLwYYmfHcO8E/+dPiCsSP09P2heNqpMbf5IKdSwGCVS1Rtpcoijl3wXB8zgeBZ1PXHAmmkC1/CWRs/fh1qmvYFzb8YAiRy5q80Tyq09IaeSpQ1ydq3r95QBSJy6H4gz2OV/v2xdm1A63XEh2+6N6p2XDyzGWQrxKE41wmqRCxie7qY2xqdv4S8Cl8ldSMEpZY46A68hKIN6zrj6eMWxauwdi6ZkZfMDuh9Pn9x5gwwgfElLopIpR8fejB6G4hAQHtq2jhn5D4ccmAqNxkrB4w5k+zc53Rupk2u3MDp5T5sRkqvNyIN2kCE6i0DD9HlqkCjWV+bG9WcUiO4D7m5fWRE5f9OQ2XjeA==IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 320\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 320 bytes...
+ -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
+ read 320 bytes
+ Conn close
+ opening connection to wpy.mitec.com.mx:443...
+ opened
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
+ <- "{\"payload\":\"Z6l24tZG2YfTOQTne8NVygr/YeuVRNya8ZUCM5NvRgOEL/Mt8PO0voNnspoiFSg+RVamC4V2BipmU3spPVBg6Dr0xMpPL7ryVB9mlM4PokUdHkZTjXJHbbr1GWdyEPMYYSH0f+M1qUDO57EyUuZv8o6QSv+a/tuOrrBwsHI8cnsv+y9qt5L9LuGRMeBYvZkkK+xw53eDqYsJGoCvpk/pljCCkGU7Q/sKsLOx0MT6dA/BLVGrGeo8ngO+W/cnOigGfIZJSPFTcrUKI/Q7AsHuP+3lG6q9VAri9UJZXm5pWOg=IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 280\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 280 bytes...
+ -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
+ read 280 bytes
+ Conn close
PRE_SCRUBBED
end
def post_scrubbed
- <<-POST_SCRUBBED
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Authorize","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","amount":"11.15","currency":"MXN","reference":"721","transaction_id":"721","installments":1,"card":"[FILTERED]","expmonth":9,"expyear":2025,"cvv":"[FILTERED]","name_client":"Pedro Flores Valdes","email":"nadie@mit.test","key_session":"[FILTERED]"}IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 320\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- response: {"folio_cdp":"095492846","auth":"928468","response":"approved","message":"0C- Pago aprobado (test)","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:08 06:09:2021","operation":"Authorize"}read 320 bytes
- Conn close
- opening connection to wpy.mitec.com.mx:443...
- opened
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Capture","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","transaction_id":"721","amount":"11.15","key_session":"[FILTERED]"}IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 280\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- response: {"folio_cdp":"095492915","auth":"929151","response":"approved","message":"0C- ","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:09 06:09:2021","operation":"Capture"}read 280 bytes
- Conn close
+ <<~POST_SCRUBBED
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
+ <- "{\"payload\":\"{\"operation\":\"Authorize\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"amount\":\"11.15\",\"currency\":\"MXN\",\"reference\":\"721\",\"transaction_id\":\"721\",\"installments\":1,\"card\":\"[FILTERED]\",\"expmonth\":9,\"expyear\":2025,\"cvv\":\"[FILTERED]\",\"name_client\":\"Pedro Flores Valdes\",\"email\":\"nadie@mit.test\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 320\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 320 bytes...
+ -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
+ read 320 bytes
+
+ {\"folio_cdp\":\"095492846\",\"auth\":\"928468\",\"response\":\"approved\",\"message\":\"0C- Pago aprobado (test)\",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:08 06:09:2021\",\"operation\":\"Authorize\"}
+
+ {\"folio_cdp\":\"095492915\",\"auth\":\"929151\",\"response\":\"approved\",\"message\":\"0C- \",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:09 06:09:2021\",\"operation\":\"Capture\"}
+ Conn close
+ opening connection to wpy.mitec.com.mx:443...
+ opened
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
+ <- "{\"payload\":\"{\"operation\":\"Capture\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"transaction_id\":\"721\",\"amount\":\"11.15\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 280\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 280 bytes...
+ -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
+ read 280 bytes
+
+ {\"folio_cdp\":\"095492846\",\"auth\":\"928468\",\"response\":\"approved\",\"message\":\"0C- Pago aprobado (test)\",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:08 06:09:2021\",\"operation\":\"Authorize\"}
+
+ {\"folio_cdp\":\"095492915\",\"auth\":\"929151\",\"response\":\"approved\",\"message\":\"0C- \",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:09 06:09:2021\",\"operation\":\"Capture\"}
+ Conn close
POST_SCRUBBED
end
end
From 8dd0d541a7909317a946580a293e758bc418e1d1 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 17 Jul 2023 12:21:20 -0700
Subject: [PATCH 115/390] Nuvie/SafeCharge: Add unreferenced refund field
---
CHANGELOG | 1 +
.../billing/gateways/safe_charge.rb | 2 +-
.../gateways/remote_safe_charge_test.rb | 12 +++++++++++
test/unit/gateways/safe_charge_test.rb | 20 +++++++++++++++++++
4 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index b544b50d3e5..6e9e2b820ca 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,7 @@
* Kushki: Add Brazil as supported country [almalee24] #4829
* Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
* MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
+* SafeCharge: Add unreferenced_refund field [yunnydang] #4831
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index a317e12f64d..42ed13f3ead 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -73,10 +73,10 @@ def refund(money, authorization, options = {})
add_transaction_data('Credit', post, money, options.merge!({ currency: original_currency }))
post[:sg_CreditType] = 2
post[:sg_AuthCode] = auth
- post[:sg_TransactionID] = transaction_id
post[:sg_CCToken] = token
post[:sg_ExpMonth] = exp_month
post[:sg_ExpYear] = exp_year
+ post[:sg_TransactionID] = transaction_id unless options[:unreferenced_refund]
commit(post)
end
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 9fe16b8f38f..ee1a0295e09 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -256,6 +256,18 @@ def test_failed_refund
assert_equal 'Transaction must contain a Card/Token/Account', response.message
end
+ def test_successful_unreferenced_refund
+ option = {
+ unreferenced_refund: true
+ }
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, option)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
def test_successful_credit
response = @gateway.credit(@amount, credit_card('4444436501403986'), @options)
assert_success response
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index 3cf3852a5b7..796ee649c8a 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -203,6 +203,26 @@ def test_successful_refund
assert_equal 'Success', response.message
end
+ def test_successful_unreferenced_refund
+ refund = stub_comms do
+ @gateway.refund(@amount, 'auth|transaction_id|token|month|year|amount|currency', @options.merge(unreferenced_refund: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal(data.split('&').include?('sg_TransactionID=transaction_id'), false)
+ end.respond_with(successful_refund_response)
+
+ assert_success refund
+ end
+
+ def test_successful_refund_without_unreferenced_refund
+ refund = stub_comms do
+ @gateway.refund(@amount, 'auth|transaction_id|token|month|year|amount|currency', @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal(data.split('&').include?('sg_TransactionID=transaction_id'), true)
+ end.respond_with(successful_refund_response)
+
+ assert_success refund
+ end
+
def test_failed_refund
@gateway.expects(:ssl_post).returns(failed_refund_response)
From 7b9658e519f45e554982dfa208a9009a3fd38e9c Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Thu, 20 Jul 2023 17:19:21 -0400
Subject: [PATCH 116/390] CyberSource: include `paymentSolution` for ApplePay
and GooglePay (#4835)
See
- https://developer.cybersource.com/content/dam/docs/cybs/en-us/apple-pay/developer/fdiglobal/rest/applepay.pdf
- https://developer.cybersource.com/content/dam/docs/cybs/en-us/google-pay/developer/fdiglobal/rest/googlepay.pdf
Schema:
- https://developer.cybersource.com/library/documentation/dev_guides/Simple_Order_API_Clients/html/Topics/Using_XML1.htm
- https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.211.xsd
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 13 ++++++++
test/unit/gateways/cyber_source_test.rb | 32 +++++++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 6e9e2b820ca..1e861202158 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,7 @@
* Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
* MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
* SafeCharge: Add unreferenced_refund field [yunnydang] #4831
+* CyberSource: include `paymentSolution` for ApplePay and GooglePay [bbraschi] #4835
== Version 1.131.0 (June 21, 2023)
* Redsys: Add supported countries [jcreiff] #4811
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 4b6ea9c724e..0e031e26b1f 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -132,6 +132,11 @@ class CyberSourceGateway < Gateway
r703: 'Export hostname_country/ip_country match'
}
+ @@payment_solution = {
+ apple_pay: '001',
+ google_pay: '012'
+ }
+
# These are the options that can be used when creating a new CyberSource
# Gateway object.
#
@@ -322,6 +327,7 @@ def build_auth_request(money, creditcard_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
+ add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
@@ -393,6 +399,7 @@ def build_purchase_request(money, payment_method_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
+ add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
@@ -670,6 +677,12 @@ def add_decision_manager_fields(xml, options)
end
end
+ def add_payment_solution(xml, source)
+ return unless (payment_solution = @@payment_solution[source])
+
+ xml.tag! 'paymentSolution', payment_solution
+ end
+
def add_issuer_additional_data(xml, options)
return unless options[:issuer_additional_data]
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 53055ccd7c0..29f08a73d6f 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -214,6 +214,22 @@ def test_purchase_includes_invoice_header
end.respond_with(successful_purchase_response)
end
+ def test_purchase_with_apple_pay_includes_payment_solution_001
+ stub_comms do
+ @gateway.purchase(100, network_tokenization_credit_card('4242424242424242', source: :apple_pay))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/001<\/paymentSolution>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_google_pay_includes_payment_solution_012
+ stub_comms do
+ @gateway.purchase(100, network_tokenization_credit_card('4242424242424242', source: :google_pay))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/012<\/paymentSolution>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
def test_purchase_includes_tax_management_indicator
stub_comms do
@gateway.purchase(100, @credit_card, tax_management_indicator: 3)
@@ -280,6 +296,22 @@ def test_authorize_includes_customer_id
end.respond_with(successful_authorization_response)
end
+ def test_authorize_with_apple_pay_includes_payment_solution_001
+ stub_comms do
+ @gateway.authorize(100, network_tokenization_credit_card('4242424242424242', source: :apple_pay))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/001<\/paymentSolution>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
+ def test_authorize_with_google_pay_includes_payment_solution_012
+ stub_comms do
+ @gateway.authorize(100, network_tokenization_credit_card('4242424242424242', source: :google_pay))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/012<\/paymentSolution>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
def test_authorize_includes_merchant_tax_id_in_billing_address_but_not_shipping_address
stub_comms do
@gateway.authorize(100, @credit_card, order_id: '1', merchant_tax_id: '123')
From 643b70787cb482a401813916a589be7e3373da29 Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Thu, 20 Jul 2023 17:44:19 -0400
Subject: [PATCH 117/390] Release v1.132.0
---
CHANGELOG | 4 +++-
lib/active_merchant/version.rb | 2 +-
test/fixtures.yml | 8 ++++----
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1e861202158..a215ee7c033 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,8 +4,11 @@
== HEAD
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
+* Nuvei (formerly SafeCharge): Add customer details to credit action [yunnydang] #4820
+* IPG: Update live url to correct endpoint [curiousepic] #4121
* VPos: Adding Panal Credit Card type [jherreraa] #4814
* Stripe PI: Update parameters for creation of customer [almalee24] #4782
+* WorldPay: Update xml tag for Credit Cards [almalee24] #4797
* PaywayDotCom: update `live_url` [jcreiff] #4824
* Stripe & Stripe PI: Update login key validation [almalee24] #4816
* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
@@ -22,7 +25,6 @@
* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
* CheckoutV2: Add support for several customer data fields [rachelkirk] #4800
* Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
-* IPG: Update live url to correct endpoint [curiousepic] #4121
== Version 1.130.0 (June 13th, 2023)
* Payu Latam - Update error code method to surface network code [yunnydang] #4773
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index 424b00acbe0..106761c14a5 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.131.0'
+ VERSION = '1.132.0'
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 049b6a2fbb8..55016b9d1ff 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -269,8 +269,8 @@ culqi:
# Cybersource support to enable the recurring and pinless debit
# services on your test account.
cyber_source:
- login: X
- password: Y
+ login: "shopify_dev_test"
+ password: "3PasJd0w5+Dh/W4xcX8tS0cVZ67KO7FEp0b+IzW3tF9YkCpG/YTVspXDaq+4or21K3hQ1pULambN1we1tsGUOV3tu4z2POGgsMCiNwrm84FnnLofxtu7BFCzUpYaptpf0xMIG24awbDnt3iFFEWI1ly5sDQwzMgBnVgwPkqLmkczkEz2Ddq0oiuaOfskvlain0aP9oL7LxG7xQb6RIcBLxiAb2Hw3E1BCLB+RoikVfFNwat7goB+QQv8lrDIAztOmX34NH3HQiHpeUcqiu6RmuU3qflLor5PDD9Gs9FVNy4PxTsIRNsSMGdxCMj7SjrqZd8yFhoY0g2ogeGFnkAUUA=="
cyber_source_latam_pe:
login: merchant_id
@@ -1142,7 +1142,7 @@ raven_pac_net:
reach:
merchant_id: 'xxxxxxx'
- secret: 'xxxxxxx'
+ secret: 'xxxxxxx'
realex:
login: X
@@ -1289,7 +1289,7 @@ shift4:
# Working credentials, no need to replace
simetrik:
client_id: 'wNhJBdrKDk3vTmkQMAWi5zWN7y21adO3'
- client_secret: 'fq2riPpiDJaAwS4_UMAXZy1_nU1jNGz0F6gAFWOJFNmm_TfC8EFiHwMmGKAEDkwY'
+ client_secret: 'fq2riPpiDJaAwS4_UMAXZy1_nU1jNGz0F6gAFWOJFNmm_TfC8EFiHwMmGKAEDkwY'
# Replace with your serial numbers for the skipjack test environment
skipjack:
From de64108ff561362e192dfd8491683e7845ad677c Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Thu, 20 Jul 2023 18:25:32 -0400
Subject: [PATCH 118/390] Fix CHANGELOG after Version 1.132.0 (#4837)
---
CHANGELOG | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index a215ee7c033..ae3f597aab8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.132.0 (July 20, 2023)
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
* Nuvei (formerly SafeCharge): Add customer details to credit action [yunnydang] #4820
From 90539ee465b3827eff943c05579177f66f30fa15 Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Thu, 20 Jul 2023 18:38:26 -0400
Subject: [PATCH 119/390] CyberSource: remove credentials from tests (#4836)
---
CHANGELOG | 1 +
test/fixtures.yml | 8 ++++----
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index ae3f597aab8..fe2e1b2e10d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* CyberSource: remove credentials from tests [bbraschi] #4836
== Version 1.132.0 (July 20, 2023)
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 55016b9d1ff..049b6a2fbb8 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -269,8 +269,8 @@ culqi:
# Cybersource support to enable the recurring and pinless debit
# services on your test account.
cyber_source:
- login: "shopify_dev_test"
- password: "3PasJd0w5+Dh/W4xcX8tS0cVZ67KO7FEp0b+IzW3tF9YkCpG/YTVspXDaq+4or21K3hQ1pULambN1we1tsGUOV3tu4z2POGgsMCiNwrm84FnnLofxtu7BFCzUpYaptpf0xMIG24awbDnt3iFFEWI1ly5sDQwzMgBnVgwPkqLmkczkEz2Ddq0oiuaOfskvlain0aP9oL7LxG7xQb6RIcBLxiAb2Hw3E1BCLB+RoikVfFNwat7goB+QQv8lrDIAztOmX34NH3HQiHpeUcqiu6RmuU3qflLor5PDD9Gs9FVNy4PxTsIRNsSMGdxCMj7SjrqZd8yFhoY0g2ogeGFnkAUUA=="
+ login: X
+ password: Y
cyber_source_latam_pe:
login: merchant_id
@@ -1142,7 +1142,7 @@ raven_pac_net:
reach:
merchant_id: 'xxxxxxx'
- secret: 'xxxxxxx'
+ secret: 'xxxxxxx'
realex:
login: X
@@ -1289,7 +1289,7 @@ shift4:
# Working credentials, no need to replace
simetrik:
client_id: 'wNhJBdrKDk3vTmkQMAWi5zWN7y21adO3'
- client_secret: 'fq2riPpiDJaAwS4_UMAXZy1_nU1jNGz0F6gAFWOJFNmm_TfC8EFiHwMmGKAEDkwY'
+ client_secret: 'fq2riPpiDJaAwS4_UMAXZy1_nU1jNGz0F6gAFWOJFNmm_TfC8EFiHwMmGKAEDkwY'
# Replace with your serial numbers for the skipjack test environment
skipjack:
From 7220441c4c21f07d09591386f0a228e6177b56f5 Mon Sep 17 00:00:00 2001
From: Bertrand Braschi
Date: Thu, 20 Jul 2023 18:49:10 -0400
Subject: [PATCH 120/390] Release v1.133.0
---
CHANGELOG | 2 ++
lib/active_merchant/version.rb | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index fe2e1b2e10d..cb6caa24a1f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.133.0 (July 20, 2023)
* CyberSource: remove credentials from tests [bbraschi] #4836
== Version 1.132.0 (July 20, 2023)
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index 106761c14a5..89c8baee66b 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.132.0'
+ VERSION = '1.133.0'
end
From 79df249c972ae18a55c8d6d03b02039550cfa1b6 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Mon, 24 Jul 2023 15:18:03 -0400
Subject: [PATCH 121/390] Paysafe: Map order_id to merchantRefNum
If options[:merchant_ref_num] is not supplied, options[:order_id] will be used as the fallback value
CER-683
LOCAL
5559 tests, 77691 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
760 files inspected, no offenses detected
UNIT
18 tests, 89 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
REMOTE
33 tests, 82 assertions, 8 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
75.7576% passed
---
CHANGELOG | 1 +
.../billing/gateways/paysafe.rb | 2 +-
test/unit/gateways/paysafe_test.rb | 20 +++++++++++++++++++
3 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index cb6caa24a1f..76ccaa9a8a5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Paysafe: Map order_id to merchantRefNum [jcreiff] #4839
== Version 1.133.0 (July 20, 2023)
* CyberSource: remove credentials from tests [bbraschi] #4836
diff --git a/lib/active_merchant/billing/gateways/paysafe.rb b/lib/active_merchant/billing/gateways/paysafe.rb
index d228a7ca774..1a35de0bb3a 100644
--- a/lib/active_merchant/billing/gateways/paysafe.rb
+++ b/lib/active_merchant/billing/gateways/paysafe.rb
@@ -401,7 +401,7 @@ def get_id_from_store_auth(authorization)
def post_data(parameters = {}, options = {})
return unless parameters.present?
- parameters[:merchantRefNum] = options[:merchant_ref_num] || SecureRandom.hex(16).to_s
+ parameters[:merchantRefNum] = options[:merchant_ref_num] || options[:order_id] || SecureRandom.hex(16).to_s
parameters.to_json
end
diff --git a/test/unit/gateways/paysafe_test.rb b/test/unit/gateways/paysafe_test.rb
index b961a2c2a3e..1b4302872e9 100644
--- a/test/unit/gateways/paysafe_test.rb
+++ b/test/unit/gateways/paysafe_test.rb
@@ -243,6 +243,26 @@ def test_successful_store
assert_success response
end
+ def test_merchant_ref_num_and_order_id
+ options = @options.merge({ order_id: '12345678' })
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"merchantRefNum":"12345678"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ options = @options.merge({ order_id: '12345678', merchant_ref_num: '87654321' })
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"merchantRefNum":"87654321"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_scrub
assert @gateway.supports_scrubbing?
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
From c8c2c895ecb3ed201d067fc236a216cc74b72c17 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 11 Jul 2023 12:39:46 -0500
Subject: [PATCH 122/390] Stripe PI: Gate sending NTID
Don't send NTID in add_stored_credential if
post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_on_session'
and setup_future_usage=off_session.
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 64 +++++++++++--------
.../gateways/stripe_payment_intents_test.rb | 51 +++++++++++++++
3 files changed, 89 insertions(+), 27 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 76ccaa9a8a5..792c8c2586e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
== HEAD
* Paysafe: Map order_id to merchantRefNum [jcreiff] #4839
+* Stripe PI: Gate sending NTID [almalee24] #4828
== Version 1.133.0 (July 20, 2023)
* CyberSource: remove credentials from tests [bbraschi] #4836
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index b0e4f864741..4d281a77151 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -418,63 +418,73 @@ def add_exemption(post, options = {})
# the existing logic by default. To be able to utilize this field, you must reach out to Stripe.
def add_stored_credentials(post, options = {})
- return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
+ stored_credential = options[:stored_credential]
+ return unless stored_credential && !stored_credential.values.all?(&:nil?)
post[:payment_method_options] ||= {}
post[:payment_method_options][:card] ||= {}
- add_stored_credential_transaction_type(post, options) if options[:stored_credential_transaction_type]
- stored_credential = options[:stored_credential]
- post[:payment_method_options][:card][:mit_exemption] = {}
+ card_options = post[:payment_method_options][:card]
+ card_options[:mit_exemption] = {}
# Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
# The network_transaction_id can be sent in nested under stored credentials OR as its own field (add_ntid handles when it is sent in on its own)
# If it is sent is as its own field AND under stored credentials, the value sent under its own field is what will send.
- post[:payment_method_options][:card][:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
- post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ card_options[:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
+ unless options[:setup_future_usage] == 'off_session'
+ card_options[:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ end
+
+ add_stored_credential_transaction_type(post, options)
end
def add_stored_credential_transaction_type(post, options = {})
+ return unless options[:stored_credential_transaction_type]
+
stored_credential = options[:stored_credential]
# Do not add anything unless these are present.
return unless stored_credential[:reason_type] && stored_credential[:initiator]
# Not compatible with off_session parameter.
options.delete(:off_session)
- if stored_credential[:initial_transaction]
- # Initial transactions must by CIT
- return unless stored_credential[:initiator] == 'cardholder'
- initial_transaction_stored_credential(post, stored_credential[:reason_type])
- else
- # Subsequent transaction
- subsequent_transaction_stored_credential(post, stored_credential[:initiator], stored_credential[:reason_type])
- end
+ stored_credential_type = if stored_credential[:initial_transaction]
+ return unless stored_credential[:initiator] == 'cardholder'
+
+ initial_transaction_stored_credential(post, stored_credential)
+ else
+ subsequent_transaction_stored_credential(post, stored_credential)
+ end
+
+ card_options = post[:payment_method_options][:card]
+ card_options[:stored_credential_transaction_type] = stored_credential_type
+ card_options[:mit_exemption].delete(:network_transaction_id) if stored_credential_type == 'setup_on_session'
end
- def initial_transaction_stored_credential(post, reason_type)
- if reason_type == 'unscheduled'
+ def initial_transaction_stored_credential(post, stored_credential)
+ case stored_credential[:reason_type]
+ when 'unscheduled'
# Charge on-session and store card for future one-off payment use
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_unscheduled'
- elsif reason_type == 'recurring'
+ 'setup_off_session_unscheduled'
+ when 'recurring'
# Charge on-session and store card for future recurring payment use
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_recurring'
+ 'setup_off_session_recurring'
else
# Charge on-session and store card for future on-session payment use.
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_on_session'
+ 'setup_on_session'
end
end
- def subsequent_transaction_stored_credential(post, initiator, reason_type)
- if initiator == 'cardholder'
+ def subsequent_transaction_stored_credential(post, stored_credential)
+ if stored_credential[:initiator] == 'cardholder'
# Charge on-session customer using previously stored card.
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_on_session'
- elsif reason_type == 'recurring'
+ 'stored_on_session'
+ elsif stored_credential[:reason_type] == 'recurring'
# Charge off-session customer using previously stored card for recurring transaction
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_recurring'
+ 'stored_off_session_recurring'
else
# Charge off-session customer using previously stored card for one-off transaction
- post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_unscheduled'
+ 'stored_off_session_unscheduled'
end
end
@@ -485,7 +495,7 @@ def add_ntid(post, options = {})
post[:payment_method_options][:card] ||= {}
post[:payment_method_options][:card][:mit_exemption] = {}
- post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id] if options[:network_transaction_id]
+ post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id]
end
def add_claim_without_transaction_id(post, options = {})
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 0ed90ee1b1e..5c25ff7809b 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -344,6 +344,57 @@ def test_successful_purchase_with_level3_data
end.respond_with(successful_create_intent_response)
end
+ def test_succesful_purchase_with_stored_credentials_without_sending_ntid
+ [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
+ network_transaction_id = '1098510912210968'
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, card_to_use, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ off_session: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'installment',
+ initial_transaction: true,
+ network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/
+ ds_transaction_id: 'null' # this is optional and can be null if not available.
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=}, data)
+ assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data)
+ end.respond_with(successful_create_intent_response)
+ end
+ end
+
+ def test_succesful_purchase_with_ntid_when_off_session
+ # don't send NTID if setup_future_usage == off_session
+ [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
+ network_transaction_id = '1098510912210968'
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, card_to_use, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ off_session: true,
+ setup_future_usage: 'off_session',
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'installment',
+ initial_transaction: true,
+ network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/
+ ds_transaction_id: 'null' # this is optional and can be null if not available.
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=}, data)
+ assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data)
+ end.respond_with(successful_create_intent_response)
+ end
+ end
+
def test_succesful_purchase_with_stored_credentials
[@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
network_transaction_id = '1098510912210968'
From 3b435296600ad0358bf7c6da6a4573019e01f475 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 3 Jul 2023 11:35:20 -0500
Subject: [PATCH 123/390] Update required Ruby version
Updated required Ruby version to be 2.7 and Rubocop to 0.72.0.
All unit tests and rubocop:
5532 tests, 77501 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.github/workflows/ruby-ci.yml | 2 -
.rubocop.yml | 4 +-
Gemfile | 2 +-
activemerchant.gemspec | 2 +-
circle.yml | 2 +-
lib/active_merchant/billing/check.rb | 4 +-
.../billing/gateways/airwallex.rb | 4 +-
lib/active_merchant/billing/gateways/alelo.rb | 4 +-
.../billing/gateways/authorize_net_arb.rb | 8 +-
.../billing/gateways/axcessms.rb | 8 +-
.../billing/gateways/banwire.rb | 6 +-
.../gateways/beanstream/beanstream_core.rb | 8 +-
.../billing/gateways/blue_pay.rb | 16 +-
.../billing/gateways/braintree_blue.rb | 31 +-
.../billing/gateways/card_connect.rb | 7 +-
.../billing/gateways/cyber_source.rb | 8 +-
.../billing/gateways/data_cash.rb | 38 +-
.../billing/gateways/efsnet.rb | 8 +-
.../billing/gateways/element.rb | 1 -
lib/active_merchant/billing/gateways/epay.rb | 8 +-
.../billing/gateways/evo_ca.rb | 8 +-
lib/active_merchant/billing/gateways/eway.rb | 6 +-
.../billing/gateways/eway_managed.rb | 8 +-
lib/active_merchant/billing/gateways/exact.rb | 8 +-
.../billing/gateways/federated_canada.rb | 8 +-
.../billing/gateways/firstdata_e4.rb | 8 +-
.../billing/gateways/firstdata_e4_v27.rb | 8 +-
.../billing/gateways/garanti.rb | 6 +-
.../billing/gateways/iats_payments.rb | 9 +-
.../billing/gateways/inspire.rb | 7 +-
.../billing/gateways/instapay.rb | 8 +-
.../billing/gateways/iridium.rb | 20 +-
.../billing/gateways/itransact.rb | 8 +-
.../billing/gateways/ixopay.rb | 4 +-
.../billing/gateways/jetpay.rb | 6 +-
.../billing/gateways/jetpay_v2.rb | 6 +-
.../billing/gateways/linkpoint.rb | 8 +-
.../billing/gateways/merchant_e_solutions.rb | 8 +-
.../billing/gateways/merchant_ware.rb | 15 +-
.../gateways/merchant_ware_version_four.rb | 15 +-
.../billing/gateways/mercury.rb | 8 +-
.../billing/gateways/metrics_global.rb | 8 +-
lib/active_merchant/billing/gateways/migs.rb | 8 +-
.../billing/gateways/modern_payments_cim.rb | 28 +-
.../billing/gateways/money_movers.rb | 8 +-
.../billing/gateways/nab_transact.rb | 16 +-
.../billing/gateways/net_registry.rb | 8 +-
.../billing/gateways/netbilling.rb | 8 +-
.../billing/gateways/network_merchants.rb | 8 +-
.../billing/gateways/openpay.rb | 6 +-
lib/active_merchant/billing/gateways/opp.rb | 3 +-
.../billing/gateways/optimal_payment.rb | 8 +-
.../billing/gateways/orbital.rb | 8 +-
.../billing/gateways/pac_net_raven.rb | 8 +-
.../billing/gateways/pay_gate_xml.rb | 8 +-
.../billing/gateways/pay_hub.rb | 6 +-
.../billing/gateways/pay_junction.rb | 8 +-
.../billing/gateways/pay_secure.rb | 8 +-
lib/active_merchant/billing/gateways/payex.rb | 6 +-
.../billing/gateways/payment_express.rb | 8 +-
.../billing/gateways/payscout.rb | 8 +-
.../billing/gateways/paystation.rb | 8 +-
.../billing/gateways/payway.rb | 8 +-
.../billing/gateways/payway_dot_com.rb | 2 +-
.../billing/gateways/plugnpay.rb | 8 +-
.../billing/gateways/psigate.rb | 8 +-
.../billing/gateways/psl_card.rb | 8 +-
lib/active_merchant/billing/gateways/qbms.rb | 8 +-
.../billing/gateways/quantum.rb | 8 +-
.../billing/gateways/quickbooks.rb | 4 +-
.../billing/gateways/quickpay/quickpay_v10.rb | 8 +-
.../gateways/quickpay/quickpay_v4to7.rb | 8 +-
lib/active_merchant/billing/gateways/sage.rb | 16 +-
.../billing/gateways/sage_pay.rb | 8 +-
.../billing/gateways/sallie_mae.rb | 8 +-
.../billing/gateways/secure_net.rb | 8 +-
.../billing/gateways/secure_pay.rb | 8 +-
.../billing/gateways/secure_pay_au.rb | 16 +-
.../billing/gateways/secure_pay_tech.rb | 8 +-
.../billing/gateways/securion_pay.rb | 6 +-
.../billing/gateways/simetrik.rb | 8 +-
.../billing/gateways/skip_jack.rb | 8 +-
.../billing/gateways/smart_ps.rb | 8 +-
.../billing/gateways/so_easy_pay.rb | 6 +-
.../billing/gateways/stripe.rb | 6 +-
.../billing/gateways/swipe_checkout.rb | 6 +-
.../billing/gateways/trust_commerce.rb | 8 +-
.../billing/gateways/usa_epay_advanced.rb | 17 +-
.../billing/gateways/usa_epay_transaction.rb | 8 +-
.../billing/gateways/verifi.rb | 8 +-
.../billing/gateways/viaklix.rb | 8 +-
lib/active_merchant/billing/gateways/vpos.rb | 4 +-
.../billing/gateways/wirecard.rb | 10 +-
.../billing/gateways/worldpay.rb | 7 +-
.../gateways/worldpay_online_payments.rb | 12 +-
lib/support/ssl_verify.rb | 8 +-
lib/support/ssl_version.rb | 12 +-
test/remote/gateways/remote_adyen_test.rb | 129 ++++---
.../remote_authorize_net_apple_pay_test.rb | 6 +-
.../gateways/remote_authorize_net_test.rb | 27 +-
.../remote_barclaycard_smartpay_test.rb | 24 +-
.../gateways/remote_braintree_blue_test.rb | 60 ++--
.../gateways/remote_card_stream_test.rb | 36 +-
.../gateways/remote_checkout_v2_test.rb | 92 ++---
.../gateways/remote_commerce_hub_test.rb | 18 +-
test/remote/gateways/remote_credorax_test.rb | 18 +-
.../gateways/remote_cyber_source_rest_test.rb | 5 +-
.../gateways/remote_cyber_source_test.rb | 73 ++--
test/remote/gateways/remote_d_local_test.rb | 15 +-
test/remote/gateways/remote_element_test.rb | 12 +-
.../remote/gateways/remote_eway_rapid_test.rb | 7 +-
test/remote/gateways/remote_eway_test.rb | 4 +-
.../gateways/remote_firstdata_e4_test.rb | 6 +-
.../gateways/remote_firstdata_e4_v27_test.rb | 6 +-
.../gateways/remote_global_collect_test.rb | 18 +-
test/remote/gateways/remote_hps_test.rb | 78 +++--
test/remote/gateways/remote_linkpoint_test.rb | 7 +-
test/remote/gateways/remote_litle_test.rb | 7 +-
.../gateways/remote_mercado_pago_test.rb | 21 +-
.../remote_merchant_ware_version_four_test.rb | 6 +-
test/remote/gateways/remote_moneris_test.rb | 28 +-
.../gateways/remote_net_registry_test.rb | 4 +-
test/remote/gateways/remote_nmi_test.rb | 6 +-
test/remote/gateways/remote_orbital_test.rb | 36 +-
.../gateways/remote_pay_junction_test.rb | 16 +-
test/remote/gateways/remote_payflow_test.rb | 7 +-
test/remote/gateways/remote_paymentez_test.rb | 6 +-
test/remote/gateways/remote_paypal_test.rb | 6 +-
test/remote/gateways/remote_payway_test.rb | 36 +-
test/remote/gateways/remote_psl_card_test.rb | 34 +-
test/remote/gateways/remote_realex_test.rb | 171 ++++++---
test/remote/gateways/remote_sage_pay_test.rb | 11 +-
test/remote/gateways/remote_sage_test.rb | 9 +-
.../remote/gateways/remote_secure_pay_test.rb | 4 +-
test/remote/gateways/remote_skipjack_test.rb | 3 +-
.../remote_stripe_android_pay_test.rb | 12 +-
.../gateways/remote_stripe_apple_pay_test.rb | 24 +-
.../remote_stripe_payment_intents_test.rb | 38 +-
test/remote/gateways/remote_worldpay_test.rb | 36 +-
test/test_helper.rb | 9 +-
test/unit/credit_card_test.rb | 2 +-
test/unit/fixtures_test.rb | 2 +-
test/unit/gateways/adyen_test.rb | 36 +-
test/unit/gateways/authorize_net_arb_test.rb | 7 +-
test/unit/gateways/authorize_net_test.rb | 18 +-
test/unit/gateways/banwire_test.rb | 12 +-
.../gateways/barclaycard_smartpay_test.rb | 8 +-
test/unit/gateways/blue_pay_test.rb | 10 +-
test/unit/gateways/braintree_blue_test.rb | 32 +-
test/unit/gateways/card_stream_test.rb | 16 +-
test/unit/gateways/commerce_hub_test.rb | 18 +-
test/unit/gateways/credorax_test.rb | 12 +-
test/unit/gateways/cyber_source_rest_test.rb | 6 +-
test/unit/gateways/cyber_source_test.rb | 52 +--
test/unit/gateways/d_local_test.rb | 18 +-
test/unit/gateways/ebanx_test.rb | 2 +-
test/unit/gateways/epay_test.rb | 6 +-
test/unit/gateways/eway_rapid_test.rb | 7 +-
test/unit/gateways/exact_test.rb | 7 +-
test/unit/gateways/firstdata_e4_test.rb | 7 +-
test/unit/gateways/firstdata_e4_v27_test.rb | 4 +-
test/unit/gateways/garanti_test.rb | 2 +-
test/unit/gateways/gateway_test.rb | 3 +-
test/unit/gateways/global_collect_test.rb | 18 +-
test/unit/gateways/hps_test.rb | 144 +++++---
test/unit/gateways/mercado_pago_test.rb | 18 +-
test/unit/gateways/moneris_test.rb | 26 +-
test/unit/gateways/nmi_test.rb | 6 +-
test/unit/gateways/opp_test.rb | 24 +-
test/unit/gateways/orbital_test.rb | 39 +--
test/unit/gateways/pac_net_raven_test.rb | 4 +-
test/unit/gateways/paybox_direct_test.rb | 3 +-
test/unit/gateways/payeezy_test.rb | 14 +-
test/unit/gateways/payflow_test.rb | 14 +-
test/unit/gateways/paymentez_test.rb | 6 +-
.../gateways/paypal/paypal_common_api_test.rb | 12 +-
.../gateways/paypal_digital_goods_test.rb | 58 +--
test/unit/gateways/paypal_express_test.rb | 330 ++++++++++--------
test/unit/gateways/paypal_test.rb | 16 +-
test/unit/gateways/payway_test.rb | 2 +-
test/unit/gateways/realex_test.rb | 3 +-
test/unit/gateways/sage_pay_test.rb | 4 +-
.../gateways/stripe_payment_intents_test.rb | 10 +-
test/unit/gateways/stripe_test.rb | 30 +-
test/unit/gateways/worldpay_test.rb | 52 +--
185 files changed, 1958 insertions(+), 1163 deletions(-)
diff --git a/.github/workflows/ruby-ci.yml b/.github/workflows/ruby-ci.yml
index adc9677b38b..1275083a680 100644
--- a/.github/workflows/ruby-ci.yml
+++ b/.github/workflows/ruby-ci.yml
@@ -17,8 +17,6 @@ jobs:
strategy:
matrix:
version:
- - 2.5
- - 2.6
- 2.7
gemfile:
- gemfiles/Gemfile.rails50
diff --git a/.rubocop.yml b/.rubocop.yml
index 50d63c3dd84..f012a5c1777 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -15,7 +15,7 @@ AllCops:
- "lib/active_merchant/billing/gateways/paypal_express.rb"
- "vendor/**/*"
ExtraDetails: false
- TargetRubyVersion: 2.5
+ TargetRubyVersion: 2.7
# Active Merchant gateways are not amenable to length restrictions
Metrics/ClassLength:
@@ -33,7 +33,7 @@ Layout/DotPosition:
Layout/CaseIndentation:
EnforcedStyle: end
-Layout/IndentHash:
+Layout/IndentFirstHashElement:
EnforcedStyle: consistent
Naming/PredicateName:
diff --git a/Gemfile b/Gemfile
index 174afc778d3..01084b00034 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
gemspec
gem 'jruby-openssl', platforms: :jruby
-gem 'rubocop', '~> 0.62.0', require: false
+gem 'rubocop', '~> 0.72.0', require: false
group :test, :remote_test do
# gateway-specific dependencies, keeping these gems out of the gemspec
diff --git a/activemerchant.gemspec b/activemerchant.gemspec
index 3aed581dc47..a1e8ed4f5b6 100644
--- a/activemerchant.gemspec
+++ b/activemerchant.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.email = 'tobi@leetsoft.com'
s.homepage = 'http://activemerchant.org/'
- s.required_ruby_version = '>= 2.5'
+ s.required_ruby_version = '>= 2.7'
s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'CONTRIBUTORS', 'lib/**/*', 'vendor/**/*']
s.require_path = 'lib'
diff --git a/circle.yml b/circle.yml
index fcf9fe6fa42..949fa18bb15 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,6 +1,6 @@
machine:
ruby:
- version: '2.5.0'
+ version: '2.7.0'
dependencies:
cache_directories:
diff --git a/lib/active_merchant/billing/check.rb b/lib/active_merchant/billing/check.rb
index 940ab14e57b..1d6feb931f2 100644
--- a/lib/active_merchant/billing/check.rb
+++ b/lib/active_merchant/billing/check.rb
@@ -7,8 +7,8 @@ module Billing #:nodoc:
# You may use Check in place of CreditCard with any gateway that supports it.
class Check < Model
attr_accessor :first_name, :last_name,
- :bank_name, :routing_number, :account_number,
- :account_holder_type, :account_type, :number
+ :bank_name, :routing_number, :account_number,
+ :account_holder_type, :account_type, :number
# Used for Canadian bank accounts
attr_accessor :institution_number, :transit_number
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index e60f5d8de96..fd1e06f427d 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -139,7 +139,9 @@ def setup_access_token
def build_request_url(action, id = nil)
base_url = (test? ? test_url : live_url)
- base_url + ENDPOINTS[action].to_s % { id: id }
+ endpoint = ENDPOINTS[action].to_s
+ endpoint = id.present? ? endpoint % { id: id } : endpoint
+ base_url + endpoint
end
def add_referrer_data(post)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index 69086bc77fd..fd1cfc11a5f 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -144,9 +144,9 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
- rescue ResponseError => error
+ rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise error unless try_again && %w(401 404).include?(error.response.code) && @options[:access_token].present?
+ raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
@options.delete(:access_token)
@options.delete(:encryption_key)
diff --git a/lib/active_merchant/billing/gateways/authorize_net_arb.rb b/lib/active_merchant/billing/gateways/authorize_net_arb.rb
index 5bcf08b8107..85a5d6c4b10 100644
--- a/lib/active_merchant/billing/gateways/authorize_net_arb.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net_arb.rb
@@ -393,9 +393,13 @@ def recurring_commit(action, request)
test_mode = test? || message =~ /Test Mode/
success = response[:result_code] == 'Ok'
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test_mode,
- authorization: response[:subscription_id])
+ authorization: response[:subscription_id]
+ )
end
def recurring_parse(action, xml)
diff --git a/lib/active_merchant/billing/gateways/axcessms.rb b/lib/active_merchant/billing/gateways/axcessms.rb
index b3e113338a6..eff4b112086 100644
--- a/lib/active_merchant/billing/gateways/axcessms.rb
+++ b/lib/active_merchant/billing/gateways/axcessms.rb
@@ -71,9 +71,13 @@ def commit(paymentcode, money, payment, options)
message = "#{response[:reason]} - #{response[:return]}"
authorization = response[:unique_id]
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
authorization: authorization,
- test: (response[:mode] != 'LIVE'))
+ test: (response[:mode] != 'LIVE')
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/banwire.rb b/lib/active_merchant/billing/gateways/banwire.rb
index 6e669c8eef1..d4e784361d2 100644
--- a/lib/active_merchant/billing/gateways/banwire.rb
+++ b/lib/active_merchant/billing/gateways/banwire.rb
@@ -89,11 +89,13 @@ def commit(money, parameters)
response = json_error(raw_response)
end
- Response.new(success?(response),
+ Response.new(
+ success?(response),
response['message'],
response,
test: test?,
- authorization: response['code_auth'])
+ authorization: response['code_auth']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
index b794899c579..2239c87a75d 100644
--- a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
+++ b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
@@ -413,11 +413,15 @@ def recurring_commit(params)
def post(data, use_profile_api = nil)
response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : self.live_url), data))
response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
- build_response(success?(response), message_from(response), response,
+ build_response(
+ success?(response),
+ message_from(response),
+ response,
test: test? || response[:authCode] == 'TEST',
authorization: authorization_from(response),
cvv_result: CVD_CODES[response[:cvdId]],
- avs_result: { code: AVS_CODES.include?(response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] })
+ avs_result: { code: AVS_CODES.include?(response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] }
+ )
end
def recurring_post(data)
diff --git a/lib/active_merchant/billing/gateways/blue_pay.rb b/lib/active_merchant/billing/gateways/blue_pay.rb
index 60b6fc863a7..b1e60343f17 100644
--- a/lib/active_merchant/billing/gateways/blue_pay.rb
+++ b/lib/active_merchant/billing/gateways/blue_pay.rb
@@ -344,9 +344,13 @@ def parse_recurring(response_fields, opts = {}) # expected status?
success = parsed[:status] != 'error'
message = parsed[:status]
- Response.new(success, message, parsed,
+ Response.new(
+ success,
+ message,
+ parsed,
test: test?,
- authorization: parsed[:rebill_id])
+ authorization: parsed[:rebill_id]
+ )
end
def parse(body)
@@ -364,11 +368,15 @@ def parse(body)
# normalize message
message = message_from(parsed)
success = parsed[:response_code] == '1'
- Response.new(success, message, parsed,
+ Response.new(
+ success,
+ message,
+ parsed,
test: test?,
authorization: (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]),
avs_result: { code: parsed[:avs_result_code] },
- cvv_result: parsed[:card_code])
+ cvv_result: parsed[:card_code]
+ )
end
def message_from(parsed)
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index c4ba7524fde..3cbe60d309f 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -193,15 +193,20 @@ def update(vault_id, creditcard, options = {})
}
}, options)[:credit_card]
- result = @braintree_gateway.customer.update(vault_id,
+ result = @braintree_gateway.customer.update(
+ vault_id,
first_name: creditcard.first_name,
last_name: creditcard.last_name,
email: scrub_email(options[:email]),
phone: phone_from(options),
- credit_card: credit_card_params)
- Response.new(result.success?, message_from_result(result),
+ credit_card: credit_card_params
+ )
+ Response.new(
+ result.success?,
+ message_from_result(result),
braintree_customer: (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?),
- customer_vault_id: (result.customer.id if result.success?))
+ customer_vault_id: (result.customer.id if result.success?)
+ )
end
end
@@ -271,13 +276,16 @@ def add_customer_with_credit_card(creditcard, options)
device_data: options[:device_data]
}.merge credit_card_params
result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options))
- Response.new(result.success?, message_from_result(result),
+ Response.new(
+ result.success?,
+ message_from_result(result),
{
braintree_customer: (customer_hash(result.customer, :include_credit_cards) if result.success?),
customer_vault_id: (result.customer.id if result.success?),
credit_card_token: (result.customer.credit_cards[0].token if result.success?)
},
- authorization: (result.customer.id if result.success?))
+ authorization: (result.customer.id if result.success?)
+ )
end
end
@@ -371,8 +379,8 @@ def map_address(address)
def commit(&block)
yield
- rescue Braintree::BraintreeError => ex
- Response.new(false, ex.class.to_s)
+ rescue Braintree::BraintreeError => e
+ Response.new(false, e.class.to_s)
end
def message_from_result(result)
@@ -901,13 +909,16 @@ def add_bank_account_to_customer(payment_method, options)
message = message_from_result(result)
message = not_verified_reason(result.payment_method) unless verified
- Response.new(verified, message,
+ Response.new(
+ verified,
+ message,
{
customer_vault_id: options[:customer],
bank_account_token: result.payment_method&.token,
verified: verified
},
- authorization: result.payment_method&.token)
+ authorization: result.payment_method&.token
+ )
end
def not_verified_reason(bank_account)
diff --git a/lib/active_merchant/billing/gateways/card_connect.rb b/lib/active_merchant/billing/gateways/card_connect.rb
index 8895e72bad3..6a803aeb322 100644
--- a/lib/active_merchant/billing/gateways/card_connect.rb
+++ b/lib/active_merchant/billing/gateways/card_connect.rb
@@ -146,9 +146,12 @@ def store(payment, options = {})
def unstore(authorization, options = {})
account_id, profile_id = authorization.split('|')
- commit('profile', {},
+ commit(
+ 'profile',
+ {},
verb: :delete,
- path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}")
+ path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}"
+ )
end
def supports_scrubbing?
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 0e031e26b1f..dce4ed14941 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -1068,12 +1068,16 @@ def commit(request, action, amount, options)
message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
fraud_review: in_fraud_review?(response),
avs_result: { code: response[:avsCode] },
- cvv_result: response[:cvCode])
+ cvv_result: response[:cvCode]
+ )
end
def auto_void?(authorization, response, message, options = {})
diff --git a/lib/active_merchant/billing/gateways/data_cash.rb b/lib/active_merchant/billing/gateways/data_cash.rb
index 46058035510..b0bbe98f266 100644
--- a/lib/active_merchant/billing/gateways/data_cash.rb
+++ b/lib/active_merchant/billing/gateways/data_cash.rb
@@ -235,23 +235,23 @@ def add_credit_card(xml, credit_card, address)
# a predefined one
xml.tag! :ExtendedPolicy do
xml.tag! :cv2_policy,
- notprovided: POLICY_REJECT,
- notchecked: POLICY_REJECT,
- matched: POLICY_ACCEPT,
- notmatched: POLICY_REJECT,
- partialmatch: POLICY_REJECT
+ notprovided: POLICY_REJECT,
+ notchecked: POLICY_REJECT,
+ matched: POLICY_ACCEPT,
+ notmatched: POLICY_REJECT,
+ partialmatch: POLICY_REJECT
xml.tag! :postcode_policy,
- notprovided: POLICY_ACCEPT,
- notchecked: POLICY_ACCEPT,
- matched: POLICY_ACCEPT,
- notmatched: POLICY_REJECT,
- partialmatch: POLICY_ACCEPT
+ notprovided: POLICY_ACCEPT,
+ notchecked: POLICY_ACCEPT,
+ matched: POLICY_ACCEPT,
+ notmatched: POLICY_REJECT,
+ partialmatch: POLICY_ACCEPT
xml.tag! :address_policy,
- notprovided: POLICY_ACCEPT,
- notchecked: POLICY_ACCEPT,
- matched: POLICY_ACCEPT,
- notmatched: POLICY_REJECT,
- partialmatch: POLICY_ACCEPT
+ notprovided: POLICY_ACCEPT,
+ notchecked: POLICY_ACCEPT,
+ matched: POLICY_ACCEPT,
+ notmatched: POLICY_REJECT,
+ partialmatch: POLICY_ACCEPT
end
end
end
@@ -260,9 +260,13 @@ def add_credit_card(xml, credit_card, address)
def commit(request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request))
- Response.new(response[:status] == '1', response[:reason], response,
+ Response.new(
+ response[:status] == '1',
+ response[:reason],
+ response,
test: test?,
- authorization: "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}")
+ authorization: "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}"
+ )
end
def format_date(month, year)
diff --git a/lib/active_merchant/billing/gateways/efsnet.rb b/lib/active_merchant/billing/gateways/efsnet.rb
index 00a5579c61f..d3ec02270ce 100644
--- a/lib/active_merchant/billing/gateways/efsnet.rb
+++ b/lib/active_merchant/billing/gateways/efsnet.rb
@@ -145,11 +145,15 @@ def add_creditcard(post, creditcard)
def commit(action, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml'))
- Response.new(success?(response), message_from(response[:result_message]), response,
+ Response.new(
+ success?(response),
+ message_from(response[:result_message]),
+ response,
test: test?,
authorization: authorization_from(response, parameters),
avs_result: { code: response[:avs_response_code] },
- cvv_result: response[:cvv_response_code])
+ cvv_result: response[:cvv_response_code]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb
index f9fe19149bf..3a833406430 100644
--- a/lib/active_merchant/billing/gateways/element.rb
+++ b/lib/active_merchant/billing/gateways/element.rb
@@ -363,7 +363,6 @@ def build_soap_request
xml['soap'].Envelope('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/') do
-
xml['soap'].Body do
yield(xml)
end
diff --git a/lib/active_merchant/billing/gateways/epay.rb b/lib/active_merchant/billing/gateways/epay.rb
index d26382cfe3c..83c35088833 100644
--- a/lib/active_merchant/billing/gateways/epay.rb
+++ b/lib/active_merchant/billing/gateways/epay.rb
@@ -174,17 +174,21 @@ def commit(action, params)
response = send("do_#{action}", params)
if action == :authorize
- Response.new response['accept'].to_i == 1,
+ Response.new(
+ response['accept'].to_i == 1,
response['errortext'],
response,
test: test?,
authorization: response['tid']
+ )
else
- Response.new response['result'] == 'true',
+ Response.new(
+ response['result'] == 'true',
messages(response['epay'], response['pbs']),
response,
test: test?,
authorization: params[:transaction]
+ )
end
end
diff --git a/lib/active_merchant/billing/gateways/evo_ca.rb b/lib/active_merchant/billing/gateways/evo_ca.rb
index 8aeabf72977..cd9848eb2a7 100644
--- a/lib/active_merchant/billing/gateways/evo_ca.rb
+++ b/lib/active_merchant/billing/gateways/evo_ca.rb
@@ -279,11 +279,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/eway.rb b/lib/active_merchant/billing/gateways/eway.rb
index 3032bb2d4fe..c6e21c658af 100644
--- a/lib/active_merchant/billing/gateways/eway.rb
+++ b/lib/active_merchant/billing/gateways/eway.rb
@@ -111,11 +111,13 @@ def commit(url, money, parameters)
raw_response = ssl_post(url, post_data(parameters))
response = parse(raw_response)
- Response.new(success?(response),
+ Response.new(
+ success?(response),
message_from(response[:ewaytrxnerror]),
response,
authorization: response[:ewaytrxnnumber],
- test: test?)
+ test: test?
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/eway_managed.rb b/lib/active_merchant/billing/gateways/eway_managed.rb
index 02cd5b5cf6f..c65ad5206b0 100644
--- a/lib/active_merchant/billing/gateways/eway_managed.rb
+++ b/lib/active_merchant/billing/gateways/eway_managed.rb
@@ -222,9 +222,13 @@ def commit(action, post)
end
response = parse(raw)
- EwayResponse.new(response[:success], response[:message], response,
+ EwayResponse.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
- authorization: response[:auth_code])
+ authorization: response[:auth_code]
+ )
end
# Where we build the full SOAP 1.2 request using builder
diff --git a/lib/active_merchant/billing/gateways/exact.rb b/lib/active_merchant/billing/gateways/exact.rb
index 144e3dc1359..6b99cd66e2a 100644
--- a/lib/active_merchant/billing/gateways/exact.rb
+++ b/lib/active_merchant/billing/gateways/exact.rb
@@ -158,11 +158,15 @@ def expdate(credit_card)
def commit(action, request)
response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs] },
- cvv_result: response[:cvv2])
+ cvv_result: response[:cvv2]
+ )
rescue ResponseError => e
case e.response.code
when '401'
diff --git a/lib/active_merchant/billing/gateways/federated_canada.rb b/lib/active_merchant/billing/gateways/federated_canada.rb
index 9399db829f5..43460286317 100644
--- a/lib/active_merchant/billing/gateways/federated_canada.rb
+++ b/lib/active_merchant/billing/gateways/federated_canada.rb
@@ -121,11 +121,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/firstdata_e4.rb b/lib/active_merchant/billing/gateways/firstdata_e4.rb
index aa2cb1e39c8..35191eeee72 100755
--- a/lib/active_merchant/billing/gateways/firstdata_e4.rb
+++ b/lib/active_merchant/billing/gateways/firstdata_e4.rb
@@ -354,12 +354,16 @@ def commit(action, request, credit_card = nil)
response = parse_error(e.response)
end
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: successful?(response) ? response_authorization(action, response, credit_card) : '',
avs_result: { code: response[:avs] },
cvv_result: response[:cvv2],
- error_code: standard_error_code(response))
+ error_code: standard_error_code(response)
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
index e6c438916d5..7ac0901d891 100644
--- a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
+++ b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
@@ -380,12 +380,16 @@ def commit(action, data, credit_card = nil)
response = parse_error(e.response)
end
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: successful?(response) ? response_authorization(action, response, credit_card) : '',
avs_result: { code: response[:avs] },
cvv_result: response[:cvv2],
- error_code: standard_error_code(response))
+ error_code: standard_error_code(response)
+ )
end
def headers(method, url, request)
diff --git a/lib/active_merchant/billing/gateways/garanti.rb b/lib/active_merchant/billing/gateways/garanti.rb
index 2bdd1071981..57a78d4104e 100644
--- a/lib/active_merchant/billing/gateways/garanti.rb
+++ b/lib/active_merchant/billing/gateways/garanti.rb
@@ -219,11 +219,13 @@ def commit(money, request)
success = success?(response)
- Response.new(success,
+ Response.new(
+ success,
success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})",
response,
test: test?,
- authorization: response[:order_id])
+ authorization: response[:order_id]
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/iats_payments.rb b/lib/active_merchant/billing/gateways/iats_payments.rb
index b8e6303f57d..ee758ea55fd 100644
--- a/lib/active_merchant/billing/gateways/iats_payments.rb
+++ b/lib/active_merchant/billing/gateways/iats_payments.rb
@@ -185,8 +185,13 @@ def creditcard_brand(brand)
end
def commit(action, parameters)
- response = parse(ssl_post(url(action), post_data(action, parameters),
- { 'Content-Type' => 'application/soap+xml; charset=utf-8' }))
+ response = parse(
+ ssl_post(
+ url(action),
+ post_data(action, parameters),
+ { 'Content-Type' => 'application/soap+xml; charset=utf-8' }
+ )
+ )
Response.new(
success_from(response),
diff --git a/lib/active_merchant/billing/gateways/inspire.rb b/lib/active_merchant/billing/gateways/inspire.rb
index 61f6f8c4b85..742ced15d0b 100644
--- a/lib/active_merchant/billing/gateways/inspire.rb
+++ b/lib/active_merchant/billing/gateways/inspire.rb
@@ -172,11 +172,14 @@ def commit(action, money, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(response['response'] == '1', message_from(response), response,
+ Response.new(
+ response['response'] == '1',
+ message_from(response), response,
authorization: response['transactionid'],
test: test?,
cvv_result: response['cvvresponse'],
- avs_result: { code: response['avsresponse'] })
+ avs_result: { code: response['avsresponse'] }
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/instapay.rb b/lib/active_merchant/billing/gateways/instapay.rb
index 4ca11852dd8..8045c169ad8 100644
--- a/lib/active_merchant/billing/gateways/instapay.rb
+++ b/lib/active_merchant/billing/gateways/instapay.rb
@@ -140,10 +140,14 @@ def commit(action, parameters)
data = ssl_post self.live_url, post_data(action, parameters)
response = parse(data)
- Response.new(response[:success], response[:message], response,
+ Response.new(
+ response[:success],
+ response[:message],
+ response,
authorization: response[:transaction_id],
avs_result: { code: response[:avs_result] },
- cvv_result: response[:cvv_result])
+ cvv_result: response[:cvv_result]
+ )
end
def post_data(action, parameters = {})
diff --git a/lib/active_merchant/billing/gateways/iridium.rb b/lib/active_merchant/billing/gateways/iridium.rb
index d2f3beff909..d139643f992 100644
--- a/lib/active_merchant/billing/gateways/iridium.rb
+++ b/lib/active_merchant/billing/gateways/iridium.rb
@@ -376,22 +376,32 @@ def add_merchant_data(xml, options)
def commit(request, options)
requires!(options, :action)
- response = parse(ssl_post(test? ? self.test_url : self.live_url, request,
- { 'SOAPAction' => 'https://www.thepaymentgateway.net/' + options[:action],
- 'Content-Type' => 'text/xml; charset=utf-8' }))
+ response = parse(
+ ssl_post(
+ test? ? self.test_url : self.live_url, request,
+ {
+ 'SOAPAction' => 'https://www.thepaymentgateway.net/' + options[:action],
+ 'Content-Type' => 'text/xml; charset=utf-8'
+ }
+ )
+ )
success = response[:transaction_result][:status_code] == '0'
message = response[:transaction_result][:message]
authorization = success ? [options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code]].compact.join(';') : nil
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
avs_result: {
street_match: AVS_CODE[ response[:transaction_output_data][:address_numeric_check_result] ],
postal_match: AVS_CODE[ response[:transaction_output_data][:post_code_check_result] ]
},
- cvv_result: CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ])
+ cvv_result: CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ]
+ )
end
def parse(xml)
diff --git a/lib/active_merchant/billing/gateways/itransact.rb b/lib/active_merchant/billing/gateways/itransact.rb
index 2a881e1939b..7ad416dc906 100644
--- a/lib/active_merchant/billing/gateways/itransact.rb
+++ b/lib/active_merchant/billing/gateways/itransact.rb
@@ -387,11 +387,15 @@ def commit(payload)
# the Base64 encoded payload signature!
response = parse(ssl_post(self.live_url, post_data(payload), 'Content-Type' => 'text/xml'))
- Response.new(successful?(response), response[:error_message], response,
+ Response.new(
+ successful?(response),
+ response[:error_message],
+ response,
test: test?,
authorization: response[:xid],
avs_result: { code: response[:avs_response] },
- cvv_result: response[:cvv_response])
+ cvv_result: response[:cvv_response]
+ )
end
def post_data(payload)
diff --git a/lib/active_merchant/billing/gateways/ixopay.rb b/lib/active_merchant/billing/gateways/ixopay.rb
index db928d445c9..71e299e726e 100644
--- a/lib/active_merchant/billing/gateways/ixopay.rb
+++ b/lib/active_merchant/billing/gateways/ixopay.rb
@@ -286,8 +286,8 @@ def commit(request)
response =
begin
parse(ssl_post(url, request, headers(request)))
- rescue StandardError => error
- parse(error.response.body)
+ rescue StandardError => e
+ parse(e.response.body)
end
Response.new(
diff --git a/lib/active_merchant/billing/gateways/jetpay.rb b/lib/active_merchant/billing/gateways/jetpay.rb
index 94b6d0bb224..c2b28b5968e 100644
--- a/lib/active_merchant/billing/gateways/jetpay.rb
+++ b/lib/active_merchant/billing/gateways/jetpay.rb
@@ -284,13 +284,15 @@ def commit(money, request, token = nil)
response = parse(ssl_post(url, request))
success = success?(response)
- Response.new(success,
+ Response.new(
+ success,
success ? 'APPROVED' : message_from(response),
response,
test: test?,
authorization: authorization_from(response, money, token),
avs_result: { code: response[:avs] },
- cvv_result: response[:cvv2])
+ cvv_result: response[:cvv2]
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/jetpay_v2.rb b/lib/active_merchant/billing/gateways/jetpay_v2.rb
index 19ff95a7e99..b852215f181 100644
--- a/lib/active_merchant/billing/gateways/jetpay_v2.rb
+++ b/lib/active_merchant/billing/gateways/jetpay_v2.rb
@@ -295,14 +295,16 @@ def commit(money, request, token = nil)
response = parse(ssl_post(url, request))
success = success?(response)
- Response.new(success,
+ Response.new(
+ success,
success ? 'APPROVED' : message_from(response),
response,
test: test?,
authorization: authorization_from(response, money, token),
avs_result: AVSResult.new(code: response[:avs]),
cvv_result: CVVResult.new(response[:cvv2]),
- error_code: success ? nil : error_code_from(response))
+ error_code: success ? nil : error_code_from(response)
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/linkpoint.rb b/lib/active_merchant/billing/gateways/linkpoint.rb
index dc5bf64e56f..11c1b95dc3d 100644
--- a/lib/active_merchant/billing/gateways/linkpoint.rb
+++ b/lib/active_merchant/billing/gateways/linkpoint.rb
@@ -263,11 +263,15 @@ def scrub(transcript)
def commit(money, creditcard, options = {})
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(money, creditcard, options)))
- Response.new(successful?(response), response[:message], response,
+ Response.new(
+ successful?(response),
+ response[:message],
+ response,
test: test?,
authorization: response[:ordernum],
avs_result: { code: response[:avs].to_s[2, 1] },
- cvv_result: response[:avs].to_s[3, 1])
+ cvv_result: response[:avs].to_s[3, 1]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
index 6c6b2bc85c8..a5bf6bdce81 100644
--- a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
+++ b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
@@ -189,11 +189,15 @@ def commit(action, money, parameters)
{ 'error_code' => '404', 'auth_response_text' => e.to_s }
end
- Response.new(success_from(response), message_from(response), response,
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
authorization: authorization_from(response),
test: test?,
cvv_result: response['cvv2_result'],
- avs_result: { code: response['avs_result'] })
+ avs_result: { code: response['avs_result'] }
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/merchant_ware.rb b/lib/active_merchant/billing/gateways/merchant_ware.rb
index 1781a301968..cc934aa6fd7 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware.rb
@@ -290,19 +290,26 @@ def url(v4 = false)
def commit(action, request, v4 = false)
begin
- data = ssl_post(url(v4), request,
+ data = ssl_post(
+ url(v4),
+ request,
'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => soap_action(action, v4))
+ 'SOAPAction' => soap_action(action, v4)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response)
end
- Response.new(response[:success], response[:message], response,
+ Response.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response['AVSResponse'] },
- cvv_result: response['CVResponse'])
+ cvv_result: response['CVResponse']
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
index 36635dd0f2a..9657a5631ed 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
@@ -261,19 +261,26 @@ def url
def commit(action, request)
begin
- data = ssl_post(url, request,
+ data = ssl_post(
+ url,
+ request,
'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => soap_action(action))
+ 'SOAPAction' => soap_action(action)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response, action)
end
- Response.new(response[:success], response[:message], response,
+ Response.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response['AvsResponse'] },
- cvv_result: response['CvResponse'])
+ cvv_result: response['CvResponse']
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb
index fcbca035e0c..5648640d734 100644
--- a/lib/active_merchant/billing/gateways/mercury.rb
+++ b/lib/active_merchant/billing/gateways/mercury.rb
@@ -302,12 +302,16 @@ def commit(action, request)
success = SUCCESS_CODES.include?(response[:cmd_status])
message = success ? 'Success' : message_from(response)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs_result] },
cvv_result: response[:cvv_result],
- error_code: success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]])
+ error_code: success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/metrics_global.rb b/lib/active_merchant/billing/gateways/metrics_global.rb
index 5aac5401d4b..c5b28a94990 100644
--- a/lib/active_merchant/billing/gateways/metrics_global.rb
+++ b/lib/active_merchant/billing/gateways/metrics_global.rb
@@ -175,12 +175,16 @@ def commit(action, money, parameters)
# (TESTMODE) Successful Sale
test_mode = test? || message =~ /TESTMODE/
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test_mode,
authorization: response[:transaction_id],
fraud_review: fraud_review?(response),
avs_result: { code: response[:avs_result_code] },
- cvv_result: response[:card_code])
+ cvv_result: response[:card_code]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/migs.rb b/lib/active_merchant/billing/gateways/migs.rb
index 50a254e49a3..f50b3d29de5 100644
--- a/lib/active_merchant/billing/gateways/migs.rb
+++ b/lib/active_merchant/billing/gateways/migs.rb
@@ -281,12 +281,16 @@ def response_object(response)
cvv_result_code = response[:CSCResultCode]
cvv_result_code = 'P' if cvv_result_code == 'Unsupported'
- Response.new(success?(response), response[:Message], response,
+ Response.new(
+ success?(response),
+ response[:Message],
+ response,
test: test?,
authorization: response[:TransactionNo],
fraud_review: fraud_review?(response),
avs_result: { code: avs_response_code },
- cvv_result: cvv_result_code)
+ cvv_result: cvv_result_code
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
index d5d6f9b2a27..9a8f760ec8e 100644
--- a/lib/active_merchant/billing/gateways/modern_payments_cim.rb
+++ b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
@@ -111,13 +111,12 @@ def add_credit_card(post, credit_card)
end
def build_request(action, params)
+ envelope_obj = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' }
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!
- xml.tag! 'env:Envelope',
- { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
- 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
- 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } do
-
+ xml.tag! 'env:Envelope', envelope_obj do
xml.tag! 'env:Body' do
xml.tag! action, { 'xmlns' => xmlns(action) } do
xml.tag! 'clientId', @options[:login]
@@ -146,15 +145,24 @@ def url(action)
end
def commit(action, params)
- data = ssl_post(url(action), build_request(action, params),
- { 'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => "#{xmlns(action)}#{action}" })
+ data = ssl_post(
+ url(action),
+ build_request(action, params),
+ {
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => "#{xmlns(action)}#{action}"
+ }
+ )
response = parse(action, data)
- Response.new(successful?(action, response), message_from(action, response), response,
+ Response.new(
+ successful?(action, response),
+ message_from(action, response),
+ response,
test: test?,
authorization: authorization_from(action, response),
- avs_result: { code: response[:avs_code] })
+ avs_result: { code: response[:avs_code] }
+ )
end
def authorization_from(action, response)
diff --git a/lib/active_merchant/billing/gateways/money_movers.rb b/lib/active_merchant/billing/gateways/money_movers.rb
index 0a16b4ea5dc..2ba6c35cae7 100644
--- a/lib/active_merchant/billing/gateways/money_movers.rb
+++ b/lib/active_merchant/billing/gateways/money_movers.rb
@@ -113,11 +113,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb
index 723063781a0..7c81d230bcf 100644
--- a/lib/active_merchant/billing/gateways/nab_transact.rb
+++ b/lib/active_merchant/billing/gateways/nab_transact.rb
@@ -233,16 +233,24 @@ def build_unstore_request(identification, options)
def commit(action, request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request)))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
def commit_periodic(action, request)
response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(action, request)))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/net_registry.rb b/lib/active_merchant/billing/gateways/net_registry.rb
index bb3c11d63f3..7052581b7ba 100644
--- a/lib/active_merchant/billing/gateways/net_registry.rb
+++ b/lib/active_merchant/billing/gateways/net_registry.rb
@@ -144,8 +144,12 @@ def commit(action, params)
# get gateway response
response = parse(ssl_post(self.live_url, post_data(action, params)))
- Response.new(response['status'] == 'approved', message_from(response), response,
- authorization: authorization_from(response, action))
+ Response.new(
+ response['status'] == 'approved',
+ message_from(response),
+ response,
+ authorization: authorization_from(response, action)
+ )
end
def post_data(action, params)
diff --git a/lib/active_merchant/billing/gateways/netbilling.rb b/lib/active_merchant/billing/gateways/netbilling.rb
index 08f0e57a398..dfa479a495d 100644
--- a/lib/active_merchant/billing/gateways/netbilling.rb
+++ b/lib/active_merchant/billing/gateways/netbilling.rb
@@ -193,11 +193,15 @@ def parse(body)
def commit(action, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test_response?(response),
authorization: response[:trans_id],
avs_result: { code: response[:avs_code] },
- cvv_result: response[:cvv2_code])
+ cvv_result: response[:cvv2_code]
+ )
rescue ActiveMerchant::ResponseError => e
raise unless e.response.code =~ /^[67]\d\d$/
diff --git a/lib/active_merchant/billing/gateways/network_merchants.rb b/lib/active_merchant/billing/gateways/network_merchants.rb
index 4c1c2ef16d5..a72f133dce0 100644
--- a/lib/active_merchant/billing/gateways/network_merchants.rb
+++ b/lib/active_merchant/billing/gateways/network_merchants.rb
@@ -200,11 +200,15 @@ def commit(action, parameters)
authorization = authorization_from(success, parameters, raw)
- Response.new(success, raw['responsetext'], raw,
+ Response.new(
+ success,
+ raw['responsetext'],
+ raw,
test: test?,
authorization: authorization,
avs_result: { code: raw['avsresponse'] },
- cvv_result: raw['cvvresponse'])
+ cvv_result: raw['cvvresponse']
+ )
end
def build_request(action, parameters)
diff --git a/lib/active_merchant/billing/gateways/openpay.rb b/lib/active_merchant/billing/gateways/openpay.rb
index 7b648f75e94..e655a5b599a 100644
--- a/lib/active_merchant/billing/gateways/openpay.rb
+++ b/lib/active_merchant/billing/gateways/openpay.rb
@@ -201,11 +201,13 @@ def commit(method, resource, parameters, options = {})
response = http_request(method, resource, parameters, options)
success = !error?(response)
- Response.new(success,
+ Response.new(
+ success,
(success ? response['error_code'] : response['description']),
response,
test: test?,
- authorization: response['id'])
+ authorization: response['id']
+ )
end
def http_request(method, resource, parameters = {}, options = {})
diff --git a/lib/active_merchant/billing/gateways/opp.rb b/lib/active_merchant/billing/gateways/opp.rb
index 8dc6acf83ff..38d1f3b3e18 100644
--- a/lib/active_merchant/billing/gateways/opp.rb
+++ b/lib/active_merchant/billing/gateways/opp.rb
@@ -125,8 +125,7 @@ def initialize(options = {})
def purchase(money, payment, options = {})
# debit
options[:registrationId] = payment if payment.is_a?(String)
- execute_dbpa(options[:risk_workflow] ? 'PA.CP' : 'DB',
- money, payment, options)
+ execute_dbpa(options[:risk_workflow] ? 'PA.CP' : 'DB', money, payment, options)
end
def authorize(money, payment, options = {})
diff --git a/lib/active_merchant/billing/gateways/optimal_payment.rb b/lib/active_merchant/billing/gateways/optimal_payment.rb
index d6832282535..8ec37ff413d 100644
--- a/lib/active_merchant/billing/gateways/optimal_payment.rb
+++ b/lib/active_merchant/billing/gateways/optimal_payment.rb
@@ -121,11 +121,15 @@ def commit(action, money, post)
txnRequest = escape_uri(xml)
response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}"))
- Response.new(successful?(response), message_from(response), hash_from_xml(response),
+ Response.new(
+ successful?(response),
+ message_from(response),
+ hash_from_xml(response),
test: test?,
authorization: authorization_from(response),
avs_result: { code: avs_result_from(response) },
- cvv_result: cvv_result_from(response))
+ cvv_result: cvv_result_from(response)
+ )
end
# The upstream is picky and so we can't use CGI.escape like we want to
diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb
index 9f1cbd9f281..5963b32e9fa 100644
--- a/lib/active_merchant/billing/gateways/orbital.rb
+++ b/lib/active_merchant/billing/gateways/orbital.rb
@@ -886,13 +886,17 @@ def commit(order, message_type, retry_logic = nil, trace_number = nil)
request.call(remote_url(:secondary))
end
- Response.new(success?(response, message_type), message_from(response), response,
+ Response.new(
+ success?(response, message_type),
+ message_from(response),
+ response,
{
authorization: authorization_string(response[:tx_ref_num], response[:order_id]),
test: self.test?,
avs_result: OrbitalGateway::AVSResult.new(response[:avs_resp_code]),
cvv_result: OrbitalGateway::CVVResult.new(response[:cvv2_resp_code])
- })
+ }
+ )
end
def remote_url(url = :primary)
diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb
index 2cb24b07107..56ab23cc774 100644
--- a/lib/active_merchant/billing/gateways/pac_net_raven.rb
+++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb
@@ -121,7 +121,10 @@ def commit(action, money, parameters)
test_mode = test? || message =~ /TESTMODE/
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test_mode,
authorization: response['TrackingNumber'],
fraud_review: fraud_review?(response),
@@ -129,7 +132,8 @@ def commit(action, money, parameters)
postal_match: AVS_POSTAL_CODES[response['AVSPostalResponseCode']],
street_match: AVS_ADDRESS_CODES[response['AVSAddressResponseCode']]
},
- cvv_result: CVV2_CODES[response['CVV2ResponseCode']])
+ cvv_result: CVV2_CODES[response['CVV2ResponseCode']]
+ )
end
def url(action)
diff --git a/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
index 7d97405afac..a39c79f33b9 100644
--- a/lib/active_merchant/billing/gateways/pay_gate_xml.rb
+++ b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
@@ -264,9 +264,13 @@ def parse(action, body)
def commit(action, request, authorization = nil)
response = parse(action, ssl_post(self.live_url, request))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization || response[:tid])
+ authorization: authorization || response[:tid]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/pay_hub.rb b/lib/active_merchant/billing/gateways/pay_hub.rb
index 6f66cefaac1..b6dbb6cb695 100644
--- a/lib/active_merchant/billing/gateways/pay_hub.rb
+++ b/lib/active_merchant/billing/gateways/pay_hub.rb
@@ -182,14 +182,16 @@ def commit(post)
response = json_error(raw_response)
end
- Response.new(success,
+ Response.new(
+ success,
response_message(response),
response,
test: test?,
avs_result: { code: response['AVS_RESULT_CODE'] },
cvv_result: response['VERIFICATION_RESULT_CODE'],
error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['RESPONSE_CODE']]),
- authorization: response['TRANSACTION_ID'])
+ authorization: response['TRANSACTION_ID']
+ )
end
def response_error(raw_response)
diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb
index 0017dfe99ae..ce8d66fe60b 100644
--- a/lib/active_merchant/billing/gateways/pay_junction.rb
+++ b/lib/active_merchant/billing/gateways/pay_junction.rb
@@ -337,9 +337,13 @@ def commit(action, parameters)
response = parse(ssl_post(url, post_data(action, parameters)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response[:transaction_id] || parameters[:transaction_id])
+ authorization: response[:transaction_id] || parameters[:transaction_id]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/pay_secure.rb b/lib/active_merchant/billing/gateways/pay_secure.rb
index db7de9bdb4b..3337fa24cf3 100644
--- a/lib/active_merchant/billing/gateways/pay_secure.rb
+++ b/lib/active_merchant/billing/gateways/pay_secure.rb
@@ -68,9 +68,13 @@ def add_credit_card(post, credit_card)
def commit(action, money, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test_response?(response),
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb
index 3d63b8957a0..c1449672e4b 100644
--- a/lib/active_merchant/billing/gateways/payex.rb
+++ b/lib/active_merchant/billing/gateways/payex.rb
@@ -385,11 +385,13 @@ def commit(soap_action, request)
'Content-Length' => request.size.to_s
}
response = parse(ssl_post(url, request, headers))
- Response.new(success?(response),
+ Response.new(
+ success?(response),
message_from(response),
response,
test: test?,
- authorization: build_authorization(response))
+ authorization: build_authorization(response)
+ )
end
def build_authorization(response)
diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb
index 81369f2b432..d61b5c11eef 100644
--- a/lib/active_merchant/billing/gateways/payment_express.rb
+++ b/lib/active_merchant/billing/gateways/payment_express.rb
@@ -306,9 +306,13 @@ def commit(action, request)
response = parse(ssl_post(url, request.to_s))
# Return a response
- PaymentExpressResponse.new(response[:success] == APPROVED, message_from(response), response,
+ PaymentExpressResponse.new(
+ response[:success] == APPROVED,
+ message_from(response),
+ response,
test: response[:test_mode] == '1',
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
# Response XML documentation: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#XMLTxnOutput
diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb
index 4c2f418a8c8..dec04a926f6 100644
--- a/lib/active_merchant/billing/gateways/payscout.rb
+++ b/lib/active_merchant/billing/gateways/payscout.rb
@@ -115,12 +115,16 @@ def commit(action, money, parameters)
message = message_from(response)
test_mode = (test? || message =~ /TESTMODE/)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test_mode,
authorization: response['transactionid'],
fraud_review: fraud_review?(response),
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/paystation.rb b/lib/active_merchant/billing/gateways/paystation.rb
index e7557ce5419..4ec37cff955 100644
--- a/lib/active_merchant/billing/gateways/paystation.rb
+++ b/lib/active_merchant/billing/gateways/paystation.rb
@@ -177,9 +177,13 @@ def commit(post)
response = parse(data)
message = message_from(response)
- PaystationResponse.new(success?(response), message, response,
+ PaystationResponse.new(
+ success?(response),
+ message,
+ response,
test: (response[:tm]&.casecmp('t')&.zero?),
- authorization: response[:paystation_transaction_id])
+ authorization: response[:paystation_transaction_id]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/payway.rb b/lib/active_merchant/billing/gateways/payway.rb
index e5980c8ce04..c48373ea608 100644
--- a/lib/active_merchant/billing/gateways/payway.rb
+++ b/lib/active_merchant/billing/gateways/payway.rb
@@ -192,9 +192,13 @@ def commit(action, post)
success = (params[:summary_code] ? (params[:summary_code] == '0') : (params[:response_code] == '00'))
- Response.new(success, message, params,
+ Response.new(
+ success,
+ message,
+ params,
test: (@options[:merchant].to_s == 'TEST'),
- authorization: post[:order_number])
+ authorization: post[:order_number]
+ )
rescue ActiveMerchant::ResponseError => e
raise unless e.response.code == '403'
diff --git a/lib/active_merchant/billing/gateways/payway_dot_com.rb b/lib/active_merchant/billing/gateways/payway_dot_com.rb
index 2b9775a521b..995889b53bc 100644
--- a/lib/active_merchant/billing/gateways/payway_dot_com.rb
+++ b/lib/active_merchant/billing/gateways/payway_dot_com.rb
@@ -48,7 +48,7 @@ class PaywayDotComGateway < Gateway
'I5' => 'M', # +4 and Address Match
'I6' => 'W', # +4 Match
'I7' => 'A', # Address Match
- 'I8' => 'C', # No Match
+ 'I8' => 'C' # No Match
}
PAYWAY_WS_SUCCESS = '5000'
diff --git a/lib/active_merchant/billing/gateways/plugnpay.rb b/lib/active_merchant/billing/gateways/plugnpay.rb
index e7343efde03..e383c0d4ef7 100644
--- a/lib/active_merchant/billing/gateways/plugnpay.rb
+++ b/lib/active_merchant/billing/gateways/plugnpay.rb
@@ -178,11 +178,15 @@ def commit(action, post)
success = SUCCESS_CODES.include?(response[:finalstatus])
message = success ? 'Success' : message_from(response)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: response[:orderid],
avs_result: { code: response[:avs_code] },
- cvv_result: response[:cvvresp])
+ cvv_result: response[:cvvresp]
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/psigate.rb b/lib/active_merchant/billing/gateways/psigate.rb
index 6fc9c8905e4..c383ddd0cd9 100644
--- a/lib/active_merchant/billing/gateways/psigate.rb
+++ b/lib/active_merchant/billing/gateways/psigate.rb
@@ -102,11 +102,15 @@ def scrub(transcript)
def commit(money, creditcard, options = {})
response = parse(ssl_post(url, post_data(money, creditcard, options)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: build_authorization(response),
avs_result: { code: response[:avsresult] },
- cvv_result: response[:cardidresult])
+ cvv_result: response[:cardidresult]
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/psl_card.rb b/lib/active_merchant/billing/gateways/psl_card.rb
index ec688861457..11f20a2a1ad 100644
--- a/lib/active_merchant/billing/gateways/psl_card.rb
+++ b/lib/active_merchant/billing/gateways/psl_card.rb
@@ -259,11 +259,15 @@ def parse(body)
def commit(request)
response = parse(ssl_post(self.live_url, post_data(request)))
- Response.new(response[:ResponseCode] == APPROVED, response[:Message], response,
+ Response.new(
+ response[:ResponseCode] == APPROVED,
+ response[:Message],
+ response,
test: test?,
authorization: response[:CrossReference],
cvv_result: CVV_CODE[response[:AVSCV2Check]],
- avs_result: { code: AVS_CODE[response[:AVSCV2Check]] })
+ avs_result: { code: AVS_CODE[response[:AVSCV2Check]] }
+ )
end
# Put the passed data into a format that can be submitted to PSL
diff --git a/lib/active_merchant/billing/gateways/qbms.rb b/lib/active_merchant/billing/gateways/qbms.rb
index c709905a293..354928930aa 100644
--- a/lib/active_merchant/billing/gateways/qbms.rb
+++ b/lib/active_merchant/billing/gateways/qbms.rb
@@ -142,12 +142,16 @@ def commit(action, money, parameters)
response = parse(type, data)
message = (response[:status_message] || '').strip
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response[:credit_card_trans_id],
fraud_review: fraud_review?(response),
avs_result: { code: avs_result(response) },
- cvv_result: cvv_result(response))
+ cvv_result: cvv_result(response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/quantum.rb b/lib/active_merchant/billing/gateways/quantum.rb
index 104148b662b..693bcdb9b7a 100644
--- a/lib/active_merchant/billing/gateways/quantum.rb
+++ b/lib/active_merchant/billing/gateways/quantum.rb
@@ -215,11 +215,15 @@ def commit(request, options)
authorization = success ? authorization_for(response) : nil
end
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
avs_result: { code: response[:AVSResponseCode] },
- cvv_result: response[:CVV2ResponseCode])
+ cvv_result: response[:CVV2ResponseCode]
+ )
end
# Parse the SOAP response
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 402637475e5..6d9f14f3445 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -42,10 +42,10 @@ class QuickbooksGateway < Gateway
'PMT-5001' => STANDARD_ERROR_CODE[:card_declined], # Merchant does not support given payment method
# System Error
- 'PMT-6000' => STANDARD_ERROR_CODE[:processing_error], # A temporary Issue prevented this request from being processed.
+ 'PMT-6000' => STANDARD_ERROR_CODE[:processing_error] # A temporary Issue prevented this request from being processed.
}
- FRAUD_WARNING_CODES = ['PMT-1000', 'PMT-1001', 'PMT-1002', 'PMT-1003']
+ FRAUD_WARNING_CODES = %w(PMT-1000 PMT-1001 PMT-1002 PMT-1003)
def initialize(options = {})
# Quickbooks is deprecating OAuth 1.0 on December 17, 2019.
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
index 91eeeff564e..ce71535e833 100644
--- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
@@ -153,9 +153,13 @@ def commit(action, params = {})
response = json_error(response)
end
- Response.new(success, message_from(success, response), response,
+ Response.new(
+ success,
+ message_from(success, response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
index bb00258a3f6..c4daca39787 100644
--- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
@@ -164,9 +164,13 @@ def add_finalize(post, options)
def commit(action, params)
response = parse(ssl_post(self.live_url, post_data(action, params)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response[:transaction])
+ authorization: response[:transaction]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/sage.rb b/lib/active_merchant/billing/gateways/sage.rb
index b0c56ee02e8..25817aa99f9 100644
--- a/lib/active_merchant/billing/gateways/sage.rb
+++ b/lib/active_merchant/billing/gateways/sage.rb
@@ -260,11 +260,15 @@ def commit(action, params, source)
url = url(params, source)
response = parse(ssl_post(url, post_data(action, params)), source)
- Response.new(success?(response), response[:message], response,
+ Response.new(
+ success?(response),
+ response[:message],
+ response,
test: test?,
authorization: authorization_from(response, source),
avs_result: { code: response[:avs_result] },
- cvv_result: response[:cvv_result])
+ cvv_result: response[:cvv_result]
+ )
end
def url(params, source)
@@ -380,8 +384,12 @@ def commit(action, request)
message = success ? 'Succeeded' : 'Failed'
end
- Response.new(success, message, response,
- authorization: response[:guid])
+ Response.new(
+ success,
+ message,
+ response,
+ authorization: response[:guid]
+ )
end
ENVELOPE_NAMESPACES = {
diff --git a/lib/active_merchant/billing/gateways/sage_pay.rb b/lib/active_merchant/billing/gateways/sage_pay.rb
index 24fff459089..0f062f8caab 100644
--- a/lib/active_merchant/billing/gateways/sage_pay.rb
+++ b/lib/active_merchant/billing/gateways/sage_pay.rb
@@ -346,14 +346,18 @@ def format_date(month, year)
def commit(action, parameters)
response = parse(ssl_post(url_for(action), post_data(action, parameters)))
- Response.new(response['Status'] == APPROVED, message_from(response), response,
+ Response.new(
+ response['Status'] == APPROVED,
+ message_from(response),
+ response,
test: test?,
authorization: authorization_from(response, parameters, action),
avs_result: {
street_match: AVS_CODE[response['AddressResult']],
postal_match: AVS_CODE[response['PostCodeResult']]
},
- cvv_result: CVV_CODE[response['CV2Result']])
+ cvv_result: CVV_CODE[response['CV2Result']]
+ )
end
def authorization_from(response, params, action)
diff --git a/lib/active_merchant/billing/gateways/sallie_mae.rb b/lib/active_merchant/billing/gateways/sallie_mae.rb
index af13651eae0..fa7f1f9f8c3 100644
--- a/lib/active_merchant/billing/gateways/sallie_mae.rb
+++ b/lib/active_merchant/billing/gateways/sallie_mae.rb
@@ -120,9 +120,13 @@ def commit(action, money, parameters)
end
response = parse(ssl_post(self.live_url, parameters.to_post_data) || '')
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response['refcode'])
+ authorization: response['refcode']
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/secure_net.rb b/lib/active_merchant/billing/gateways/secure_net.rb
index 474babbd600..a82a8bc2ec4 100644
--- a/lib/active_merchant/billing/gateways/secure_net.rb
+++ b/lib/active_merchant/billing/gateways/secure_net.rb
@@ -79,11 +79,15 @@ def commit(request)
data = ssl_post(url, xml, 'Content-Type' => 'text/xml')
response = parse(data)
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
authorization: build_authorization(response),
avs_result: { code: response[:avs_result_code] },
- cvv_result: response[:card_code_response_code])
+ cvv_result: response[:card_code_response_code]
+ )
end
def build_request(request)
diff --git a/lib/active_merchant/billing/gateways/secure_pay.rb b/lib/active_merchant/billing/gateways/secure_pay.rb
index 2ad96517097..faddf42c301 100644
--- a/lib/active_merchant/billing/gateways/secure_pay.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay.rb
@@ -56,12 +56,16 @@ def commit(action, money, parameters)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response[:transaction_id],
fraud_review: fraud_review?(response),
avs_result: { code: response[:avs_result_code] },
- cvv_result: response[:card_code])
+ cvv_result: response[:card_code]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb
index fc993767a80..13f37c96254 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_au.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb
@@ -183,9 +183,13 @@ def build_request(action, body)
def commit(action, request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request)))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def build_periodic_item(action, money, credit_card, options)
@@ -239,9 +243,13 @@ def commit_periodic(request)
my_request = build_periodic_request(request)
response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
index a866bddb17b..80f26087b9c 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_tech.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
@@ -84,9 +84,13 @@ def parse(body)
def commit(action, post)
response = parse(ssl_post(self.live_url, post_data(action, post)))
- Response.new(response[:result_code] == 1, message_from(response), response,
+ Response.new(
+ response[:result_code] == 1,
+ message_from(response),
+ response,
test: test?,
- authorization: response[:merchant_transaction_reference])
+ authorization: response[:merchant_transaction_reference]
+ )
end
def message_from(result)
diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb
index 8b63029b6c0..94663c5b58b 100644
--- a/lib/active_merchant/billing/gateways/securion_pay.rb
+++ b/lib/active_merchant/billing/gateways/securion_pay.rb
@@ -225,12 +225,14 @@ def commit(url, parameters = nil, options = {}, method = nil)
response = api_request(url, parameters, options, method)
success = !response.key?('error')
- Response.new(success,
+ Response.new(
+ success,
(success ? 'Transaction approved' : response['error']['message']),
response,
test: test?,
authorization: (success ? response['id'] : response['error']['charge']),
- error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']]))
+ error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']])
+ )
end
def headers(options = {})
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index f8c34654200..5c436acab95 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -280,12 +280,12 @@ def parse(body)
def commit(action, parameters, url_params = {})
begin
response = JSON.parse ssl_post(url(action, url_params), post_data(parameters), authorized_headers())
- rescue ResponseError => exception
- case exception.response.code.to_i
+ rescue ResponseError => e
+ case e.response.code.to_i
when 400...499
- response = JSON.parse exception.response.body
+ response = JSON.parse e.response.body
else
- raise exception
+ raise e
end
end
diff --git a/lib/active_merchant/billing/gateways/skip_jack.rb b/lib/active_merchant/billing/gateways/skip_jack.rb
index 8f8851808e1..effe78d837b 100644
--- a/lib/active_merchant/billing/gateways/skip_jack.rb
+++ b/lib/active_merchant/billing/gateways/skip_jack.rb
@@ -263,11 +263,15 @@ def commit(action, money, parameters)
response = parse(ssl_post(url_for(action), post_data(action, money, parameters)), action)
# Pass along the original transaction id in the case an update transaction
- Response.new(response[:success], message_from(response, action), response,
+ Response.new(
+ response[:success],
+ message_from(response, action),
+ response,
test: test?,
authorization: response[:szTransactionFileName] || parameters[:szTransactionId],
avs_result: { code: response[:szAVSResponseCode] },
- cvv_result: response[:szCVV2ResponseCode])
+ cvv_result: response[:szCVV2ResponseCode]
+ )
end
def url_for(action)
diff --git a/lib/active_merchant/billing/gateways/smart_ps.rb b/lib/active_merchant/billing/gateways/smart_ps.rb
index 1c63532b9e0..79d116be921 100644
--- a/lib/active_merchant/billing/gateways/smart_ps.rb
+++ b/lib/active_merchant/billing/gateways/smart_ps.rb
@@ -226,11 +226,15 @@ def parse(body)
def commit(action, money, parameters)
parameters[:amount] = localized_amount(money, parameters[:currency] || default_currency) if money
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(response['response'] == '1', message_from(response), response,
+ Response.new(
+ response['response'] == '1',
+ message_from(response),
+ response,
authorization: (response['transactionid'] || response['customer_vault_id']),
test: test?,
cvv_result: response['cvvresponse'],
- avs_result: { code: response['avsresponse'] })
+ avs_result: { code: response['avsresponse'] }
+ )
end
def expdate(creditcard)
diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb
index 16b5ef79297..f13a3e6d4cc 100644
--- a/lib/active_merchant/billing/gateways/so_easy_pay.rb
+++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb
@@ -161,11 +161,13 @@ def commit(soap_action, soap, options)
'Content-Type' => 'text/xml; charset=utf-8' }
response_string = ssl_post(test? ? self.test_url : self.live_url, soap, headers)
response = parse(response_string, soap_action)
- return Response.new(response['errorcode'] == '000',
+ return Response.new(
+ response['errorcode'] == '000',
response['errormessage'],
response,
test: test?,
- authorization: response['transaction_id'])
+ authorization: response['transaction_id']
+ )
end
def build_soap(request)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index c18942ba879..da0eee50312 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -705,7 +705,8 @@ def commit(method, url, parameters = nil, options = {})
card = card_from_response(response)
avs_code = AVS_CODE_TRANSLATOR["line1: #{card['address_line1_check']}, zip: #{card['address_zip_check']}"]
cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']]
- Response.new(success,
+ Response.new(
+ success,
message_from(success, response),
response,
test: response_is_test?(response),
@@ -713,7 +714,8 @@ def commit(method, url, parameters = nil, options = {})
avs_result: { code: avs_code },
cvv_result: cvc_code,
emv_authorization: emv_authorization_from_response(response),
- error_code: success ? nil : error_code_from(response))
+ error_code: success ? nil : error_code_from(response)
+ )
end
def key_valid?(options)
diff --git a/lib/active_merchant/billing/gateways/swipe_checkout.rb b/lib/active_merchant/billing/gateways/swipe_checkout.rb
index f80621d0233..e274ce2d918 100644
--- a/lib/active_merchant/billing/gateways/swipe_checkout.rb
+++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb
@@ -104,12 +104,14 @@ def commit(action, money, parameters)
result = response['data']['result']
success = (result == 'accepted' || (test? && result == 'test-accepted'))
- Response.new(success,
+ Response.new(
+ success,
success ?
TRANSACTION_APPROVED_MSG :
TRANSACTION_DECLINED_MSG,
response,
- test: test?)
+ test: test?
+ )
else
build_error_response(message, response)
end
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index e2a3b7fbc3e..88529d90c5d 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -450,11 +450,15 @@ def commit(action, parameters)
# to be considered successful, transaction status must be either "approved" or "accepted"
success = SUCCESS_TYPES.include?(data['status'])
message = message_from(data)
- Response.new(success, message, data,
+ Response.new(
+ success,
+ message,
+ data,
test: test?,
authorization: authorization_from(action, data),
cvv_result: data['cvv'],
- avs_result: { code: data['avs'] })
+ avs_result: { code: data['avs'] }
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
index 20a985a4fdd..dd16d383583 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
@@ -1030,15 +1030,17 @@ def get_account_details
# Build soap header, etc.
def build_request(action, options = {})
- soap = Builder::XmlMarkup.new
- soap.instruct!(:xml, version: '1.0', encoding: 'utf-8')
- soap.tag! 'SOAP-ENV:Envelope',
+ envelope_obj = {
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:ns1' => 'urn:usaepay',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
- 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do
+ 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/'
+ }
+ soap = Builder::XmlMarkup.new
+ soap.instruct!(:xml, version: '1.0', encoding: 'utf-8')
+ soap.tag! 'SOAP-ENV:Envelope', envelope_obj do
soap.tag! 'SOAP-ENV:Body' do
send("build_#{action}", soap, options)
end
@@ -1335,8 +1337,7 @@ def build_credit_card_or_check(soap, payment_method)
case
when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard)
build_tag soap, :string, 'CardNumber', payment_method[:method].number
- build_tag soap, :string, 'CardExpiration',
- "#{'%02d' % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}"
+ build_tag soap, :string, 'CardExpiration', "#{'%02d' % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}"
if options[:billing_address]
build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1]
build_tag soap, :string, 'AvsZip', options[:billing_address][:zip]
@@ -1520,8 +1521,8 @@ def commit(action, request)
begin
soap = ssl_post(url, request, 'Content-Type' => 'text/xml')
- rescue ActiveMerchant::ResponseError => error
- soap = error.response.body
+ rescue ActiveMerchant::ResponseError => e
+ soap = e.response.body
end
build_response(action, soap)
diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
index c012758b305..1faa8291fb2 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
@@ -329,12 +329,16 @@ def commit(action, parameters)
approved = response[:status] == 'Approved'
error_code = nil
error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved
- Response.new(approved, message_from(response), response,
+ Response.new(
+ approved,
+ message_from(response),
+ response,
test: test?,
authorization: authorization_from(action, response),
cvv_result: response[:cvv2_result_code],
avs_result: { code: response[:avs_result_code] },
- error_code: error_code)
+ error_code: error_code
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/verifi.rb b/lib/active_merchant/billing/gateways/verifi.rb
index a11a6fcc279..5df036a5a77 100644
--- a/lib/active_merchant/billing/gateways/verifi.rb
+++ b/lib/active_merchant/billing/gateways/verifi.rb
@@ -194,11 +194,15 @@ def commit(trx_type, money, post)
response = parse(ssl_post(self.live_url, post_data(trx_type, post)))
- Response.new(response[:response].to_i == SUCCESS, message_from(response), response,
+ Response.new(
+ response[:response].to_i == SUCCESS,
+ message_from(response),
+ response,
test: test?,
authorization: response[:transactionid],
avs_result: { code: response[:avsresponse] },
- cvv_result: response[:cvvresponse])
+ cvv_result: response[:cvvresponse]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/viaklix.rb b/lib/active_merchant/billing/gateways/viaklix.rb
index 76bfc8e46ca..dd49530a77f 100644
--- a/lib/active_merchant/billing/gateways/viaklix.rb
+++ b/lib/active_merchant/billing/gateways/viaklix.rb
@@ -136,11 +136,15 @@ def commit(action, money, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)))
- Response.new(response['result'] == APPROVED, message_from(response), response,
+ Response.new(
+ response['result'] == APPROVED,
+ message_from(response),
+ response,
test: @options[:test] || test?,
authorization: authorization_from(response),
avs_result: { code: response['avs_response'] },
- cvv_result: response['cvv2_response'])
+ cvv_result: response['cvv2_response']
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/vpos.rb b/lib/active_merchant/billing/gateways/vpos.rb
index 982b1391345..25280eee8c3 100644
--- a/lib/active_merchant/billing/gateways/vpos.rb
+++ b/lib/active_merchant/billing/gateways/vpos.rb
@@ -158,10 +158,10 @@ def commit(action, parameters)
url = build_request_url(action)
begin
response = parse(ssl_post(url, post_data(parameters)))
- rescue ResponseError => response
+ rescue ResponseError => e
# Errors are returned with helpful data,
# but get filtered out by `ssl_post` because of their HTTP status.
- response = parse(response.response.body)
+ response = parse(e.response.body)
end
Response.new(
diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb
index 699e37177b4..60f1aa1eca8 100644
--- a/lib/active_merchant/billing/gateways/wirecard.rb
+++ b/lib/active_merchant/billing/gateways/wirecard.rb
@@ -179,11 +179,15 @@ def commit(action, money, options)
message = response[:Message]
authorization = response[:GuWID]
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
avs_result: { code: avs_code(response, options) },
- cvv_result: response[:CVCResponseCode])
+ cvv_result: response[:CVCResponseCode]
+ )
rescue ResponseError => e
if e.response.code == '401'
return Response.new(false, 'Invalid Login')
@@ -405,7 +409,7 @@ def errors_to_string(root)
'N' => 'I', # CSC Match
'U' => 'U', # Data Not Checked
'Y' => 'D', # All Data Matched
- 'Z' => 'P', # CSC and Postcode Matched
+ 'Z' => 'P' # CSC and Postcode Matched
}
# Amex have different AVS response codes to visa etc
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index a531d59e8a0..30e93ce882d 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -38,14 +38,14 @@ class WorldpayGateway < Gateway
'G' => 'C', # Address does not match, postcode not checked
'H' => 'I', # Address and postcode not provided
'I' => 'C', # Address not checked postcode does not match
- 'J' => 'C', # Address and postcode does not match
+ 'J' => 'C' # Address and postcode does not match
}
CVC_CODE_MAP = {
'A' => 'M', # CVV matches
'B' => 'P', # Not provided
'C' => 'P', # Not checked
- 'D' => 'N', # Does not match
+ 'D' => 'N' # Does not match
}
def initialize(options = {})
@@ -832,7 +832,8 @@ def headers(options)
# ensure cookie included on follow-up '3ds' and 'capture_request' calls, using the cookie saved from the preceding response
# cookie should be present in options on the 3ds and capture calls, but also still saved in the instance var in case
- cookie = options[:cookie] || @cookie || nil
+ cookie = defined?(@cookie) ? @cookie : nil
+ cookie = options[:cookie] || cookie
headers['Cookie'] = cookie if cookie
headers['Idempotency-Key'] = idempotency_key if idempotency_key
diff --git a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
index 2481dce0805..4ec743470d8 100644
--- a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
+++ b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
@@ -34,14 +34,16 @@ def capture(money, authorization, options = {})
if authorization
commit(:post, "orders/#{CGI.escape(authorization)}/capture", { 'captureAmount' => money }, options, 'capture')
else
- Response.new(false,
+ Response.new(
+ false,
'FAILED',
'FAILED',
test: test?,
authorization: false,
avs_result: {},
cvv_result: {},
- error_code: false)
+ error_code: false
+ )
end
end
@@ -170,14 +172,16 @@ def commit(method, url, parameters = nil, options = {}, type = false)
authorization = response['message']
end
- Response.new(success,
+ Response.new(
+ success,
success ? 'SUCCESS' : response['message'],
response,
test: test?,
authorization: authorization,
avs_result: {},
cvv_result: {},
- error_code: success ? nil : response['customCode'])
+ error_code: success ? nil : response['customCode']
+ )
end
def test?
diff --git a/lib/support/ssl_verify.rb b/lib/support/ssl_verify.rb
index 9f431a8da48..eb5a9c61157 100644
--- a/lib/support/ssl_verify.rb
+++ b/lib/support/ssl_verify.rb
@@ -80,9 +80,9 @@ def ssl_verify_peer?(uri)
end
return :success
- rescue OpenSSL::SSL::SSLError => ex
- return :fail, ex.inspect
- rescue Net::HTTPBadResponse, Errno::ETIMEDOUT, EOFError, SocketError, Errno::ECONNREFUSED, Timeout::Error => ex
- return :error, ex.inspect
+ rescue OpenSSL::SSL::SSLError => e
+ return :fail, e.inspect
+ rescue Net::HTTPBadResponse, Errno::ETIMEDOUT, EOFError, SocketError, Errno::ECONNREFUSED, Timeout::Error => e
+ return :error, e.inspect
end
end
diff --git a/lib/support/ssl_version.rb b/lib/support/ssl_version.rb
index 3050d09f438..7a6def3a5d5 100644
--- a/lib/support/ssl_version.rb
+++ b/lib/support/ssl_version.rb
@@ -75,12 +75,12 @@ def test_min_version(uri, min_version)
return :success
rescue Net::HTTPBadResponse
return :success # version negotiation succeeded
- rescue OpenSSL::SSL::SSLError => ex
- return :fail, ex.inspect
- rescue Interrupt => ex
+ rescue OpenSSL::SSL::SSLError => e
+ return :fail, e.inspect
+ rescue Interrupt => e
print_summary
- raise ex
- rescue StandardError => ex
- return :error, ex.inspect
+ raise e
+ rescue StandardError => e
+ return :error, e.inspect
end
end
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index 7c7e139f7ed..7567a872b26 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -12,67 +12,83 @@ def setup
@general_bank_account = check(name: 'A. Klaassen', account_number: '123456789', routing_number: 'NL13TEST0123456789')
- @credit_card = credit_card('4111111111111111',
+ @credit_card = credit_card(
+ '4111111111111111',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @avs_credit_card = credit_card('4400000000000008',
+ @avs_credit_card = credit_card(
+ '4400000000000008',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @elo_credit_card = credit_card('5066 9911 1111 1118',
+ @elo_credit_card = credit_card(
+ '5066 9911 1111 1118',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
- @three_ds_enrolled_card = credit_card('4917610000000000',
+ @three_ds_enrolled_card = credit_card(
+ '4917610000000000',
month: 3,
year: 2030,
verification_value: '737',
- brand: :visa)
+ brand: :visa
+ )
- @cabal_credit_card = credit_card('6035 2277 1642 7021',
+ @cabal_credit_card = credit_card(
+ '6035 2277 1642 7021',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @invalid_cabal_credit_card = credit_card('6035 2200 0000 0006',
+ @invalid_cabal_credit_card = credit_card(
+ '6035 2200 0000 0006',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @unionpay_credit_card = credit_card('8171 9999 0000 0000 021',
+ @unionpay_credit_card = credit_card(
+ '8171 9999 0000 0000 021',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
- @invalid_unionpay_credit_card = credit_card('8171 9999 1234 0000 921',
+ @invalid_unionpay_credit_card = credit_card(
+ '8171 9999 1234 0000 921',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
@declined_card = credit_card('4000300011112220')
@@ -148,44 +164,6 @@ def setup
}
@long_order_id = 'asdfjkl;asdfjkl;asdfj;aiwyutinvpoaieryutnmv;203987528752098375j3q-p489756ijmfpvbijpq348nmdf;vbjp3845'
-
- @sub_seller_options = {
- "subMerchant.numberOfSubSellers": '2',
- "subMerchant.subSeller1.id": '111111111',
- "subMerchant.subSeller1.name": 'testSub1',
- "subMerchant.subSeller1.street": 'Street1',
- "subMerchant.subSeller1.postalCode": '12242840',
- "subMerchant.subSeller1.city": 'Sao jose dos campos',
- "subMerchant.subSeller1.state": 'SP',
- "subMerchant.subSeller1.country": 'BRA',
- "subMerchant.subSeller1.taxId": '12312312340',
- "subMerchant.subSeller1.mcc": '5691',
- "subMerchant.subSeller1.debitSettlementBank": '1',
- "subMerchant.subSeller1.debitSettlementAgency": '1',
- "subMerchant.subSeller1.debitSettlementAccountType": '1',
- "subMerchant.subSeller1.debitSettlementAccount": '1',
- "subMerchant.subSeller1.creditSettlementBank": '1',
- "subMerchant.subSeller1.creditSettlementAgency": '1',
- "subMerchant.subSeller1.creditSettlementAccountType": '1',
- "subMerchant.subSeller1.creditSettlementAccount": '1',
- "subMerchant.subSeller2.id": '22222222',
- "subMerchant.subSeller2.name": 'testSub2',
- "subMerchant.subSeller2.street": 'Street2',
- "subMerchant.subSeller2.postalCode": '12300000',
- "subMerchant.subSeller2.city": 'Jacarei',
- "subMerchant.subSeller2.state": 'SP',
- "subMerchant.subSeller2.country": 'BRA',
- "subMerchant.subSeller2.taxId": '12312312340',
- "subMerchant.subSeller2.mcc": '5691',
- "subMerchant.subSeller2.debitSettlementBank": '1',
- "subMerchant.subSeller2.debitSettlementAgency": '1',
- "subMerchant.subSeller2.debitSettlementAccountType": '1',
- "subMerchant.subSeller2.debitSettlementAccount": '1',
- "subMerchant.subSeller2.creditSettlementBank": '1',
- "subMerchant.subSeller2.creditSettlementAgency": '1',
- "subMerchant.subSeller2.creditSettlementAccountType": '1',
- "subMerchant.subSeller2.creditSettlementAccount": '1'
- }
end
def test_successful_authorize
@@ -343,13 +321,15 @@ def test_successful_authorize_with_3ds2_app_based_request
# with rule set in merchant account to skip 3DS for cards of this brand
def test_successful_authorize_with_3ds_dynamic_rule_broken
- mastercard_threed = credit_card('5212345678901234',
+ mastercard_threed = credit_card(
+ '5212345678901234',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'mastercard')
+ brand: 'mastercard'
+ )
assert response = @gateway.authorize(@amount, mastercard_threed, @options.merge(threed_dynamic: true))
assert response.test?
refute response.authorization.blank?
@@ -1412,6 +1392,43 @@ def test_successful_authorize_with_sub_merchant_data
end
def test_successful_authorize_with_sub_merchant_sub_seller_data
+ @sub_seller_options = {
+ "subMerchant.numberOfSubSellers": '2',
+ "subMerchant.subSeller1.id": '111111111',
+ "subMerchant.subSeller1.name": 'testSub1',
+ "subMerchant.subSeller1.street": 'Street1',
+ "subMerchant.subSeller1.postalCode": '12242840',
+ "subMerchant.subSeller1.city": 'Sao jose dos campos',
+ "subMerchant.subSeller1.state": 'SP',
+ "subMerchant.subSeller1.country": 'BRA',
+ "subMerchant.subSeller1.taxId": '12312312340',
+ "subMerchant.subSeller1.mcc": '5691',
+ "subMerchant.subSeller1.debitSettlementBank": '1',
+ "subMerchant.subSeller1.debitSettlementAgency": '1',
+ "subMerchant.subSeller1.debitSettlementAccountType": '1',
+ "subMerchant.subSeller1.debitSettlementAccount": '1',
+ "subMerchant.subSeller1.creditSettlementBank": '1',
+ "subMerchant.subSeller1.creditSettlementAgency": '1',
+ "subMerchant.subSeller1.creditSettlementAccountType": '1',
+ "subMerchant.subSeller1.creditSettlementAccount": '1',
+ "subMerchant.subSeller2.id": '22222222',
+ "subMerchant.subSeller2.name": 'testSub2',
+ "subMerchant.subSeller2.street": 'Street2',
+ "subMerchant.subSeller2.postalCode": '12300000',
+ "subMerchant.subSeller2.city": 'Jacarei',
+ "subMerchant.subSeller2.state": 'SP',
+ "subMerchant.subSeller2.country": 'BRA',
+ "subMerchant.subSeller2.taxId": '12312312340',
+ "subMerchant.subSeller2.mcc": '5691',
+ "subMerchant.subSeller2.debitSettlementBank": '1',
+ "subMerchant.subSeller2.debitSettlementAgency": '1',
+ "subMerchant.subSeller2.debitSettlementAccountType": '1',
+ "subMerchant.subSeller2.debitSettlementAccount": '1',
+ "subMerchant.subSeller2.creditSettlementBank": '1',
+ "subMerchant.subSeller2.creditSettlementAgency": '1',
+ "subMerchant.subSeller2.creditSettlementAccountType": '1',
+ "subMerchant.subSeller2.creditSettlementAccount": '1'
+ }
assert response = @gateway.authorize(@amount, @avs_credit_card, @options.merge(sub_merchant_data: @sub_seller_options))
assert response.test?
refute response.authorization.blank?
diff --git a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
index 0cd7a16fe8f..836af912cb3 100644
--- a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
+++ b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
@@ -82,9 +82,11 @@ def apple_pay_payment_token(options = {})
transaction_identifier: 'uniqueidentifier123'
}.update(options)
- ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(
+ defaults[:payment_data],
payment_instrument_name: defaults[:payment_instrument_name],
payment_network: defaults[:payment_network],
- transaction_identifier: defaults[:transaction_identifier])
+ transaction_identifier: defaults[:transaction_identifier]
+ )
end
end
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index 96117d0d685..e6388238550 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -458,8 +458,7 @@ def test_successful_verify_after_store_with_custom_verify_amount
end
def test_successful_verify_with_apple_pay
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = @gateway.verify(credit_card, @options)
assert_success response
assert_equal 'This transaction has been approved', response.message
@@ -872,9 +871,11 @@ def test_dump_transcript
end
def test_successful_authorize_and_capture_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
+ credit_card = network_tokenization_credit_card(
+ '4000100011112224',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ verification_value: nil
+ )
auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
assert_equal 'This transaction has been approved', auth.message
@@ -884,9 +885,11 @@ def test_successful_authorize_and_capture_with_network_tokenization
end
def test_successful_refund_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
+ credit_card = network_tokenization_credit_card(
+ '4000100011112224',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ verification_value: nil
+ )
purchase = @gateway.purchase(@amount, credit_card, @options)
assert_success purchase
@@ -899,9 +902,11 @@ def test_successful_refund_with_network_tokenization
end
def test_successful_credit_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
+ credit_card = network_tokenization_credit_card(
+ '4000100011112224',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ verification_value: nil
+ )
response = @gateway.credit(@amount, credit_card, @options)
assert_success response
@@ -910,10 +915,12 @@ def test_successful_credit_with_network_tokenization
end
def test_network_tokenization_transcript_scrubbing
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
transcript = capture_transcript(@gateway) do
@gateway.authorize(@amount, credit_card, @options)
diff --git a/test/remote/gateways/remote_barclaycard_smartpay_test.rb b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
index ac59135ddba..1839dfe8ea9 100644
--- a/test/remote/gateways/remote_barclaycard_smartpay_test.rb
+++ b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
@@ -100,10 +100,12 @@ def setup
}
}
- @avs_credit_card = credit_card('4400000000000008',
+ @avs_credit_card = credit_card(
+ '4400000000000008',
month: 8,
year: 2018,
- verification_value: 737)
+ verification_value: 737
+ )
@avs_address = @options.clone
@avs_address.update(billing_address: {
@@ -158,25 +160,31 @@ def test_failed_purchase
end
def test_successful_purchase_with_unusual_address
- response = @gateway.purchase(@amount,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options_with_alternate_address)
+ @options_with_alternate_address
+ )
assert_success response
assert_equal '[capture-received]', response.message
end
def test_successful_purchase_with_house_number_and_street
- response = @gateway.purchase(@amount,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options.merge(street: 'Top Level Drive', house_number: '100'))
+ @options.merge(street: 'Top Level Drive', house_number: '100')
+ )
assert_success response
assert_equal '[capture-received]', response.message
end
def test_successful_purchase_with_no_address
- response = @gateway.purchase(@amount,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options_with_no_address)
+ @options_with_no_address
+ )
assert_success response
assert_equal '[capture-received]', response.message
end
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 1cab2cd4ac6..0ec9a6c33f7 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -470,8 +470,7 @@ def test_cvv_no_match
end
def test_successful_purchase_with_email
- assert response = @gateway.purchase(@amount, @credit_card,
- email: 'customer@example.com')
+ assert response = @gateway.purchase(@amount, @credit_card, email: 'customer@example.com')
assert_success response
transaction = response.params['braintree_transaction']
assert_equal 'customer@example.com', transaction['customer_details']['email']
@@ -570,9 +569,7 @@ def test_purchase_with_transaction_source
def test_purchase_using_specified_payment_method_token
assert response = @gateway.store(
- credit_card('4111111111111111',
- first_name: 'Old First', last_name: 'Old Last',
- month: 9, year: 2012),
+ credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012),
email: 'old@example.com',
phone: '321-654-0987'
)
@@ -604,9 +601,12 @@ def test_successful_purchase_with_addresses
zip: '60103',
country_name: 'Mexico'
}
- assert response = @gateway.purchase(@amount, @credit_card,
+ assert response = @gateway.purchase(
+ @amount,
+ @credit_card,
billing_address: billing_address,
- shipping_address: shipping_address)
+ shipping_address: shipping_address
+ )
assert_success response
transaction = response.params['braintree_transaction']
assert_equal '1 E Main St', transaction['billing_details']['street_address']
@@ -627,15 +627,13 @@ def test_successful_purchase_with_addresses
def test_successful_purchase_with_three_d_secure_pass_thru
three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' }
- response = @gateway.purchase(@amount, @credit_card,
- three_d_secure: three_d_secure_params)
+ response = @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_params)
assert_success response
end
def test_successful_purchase_with_some_three_d_secure_pass_thru_fields
three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id' }
- response = @gateway.purchase(@amount, @credit_card,
- three_d_secure: three_d_secure_params)
+ response = @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_params)
assert_success response
end
@@ -669,10 +667,12 @@ def test_authorize_and_capture
end
def test_authorize_and_capture_with_apple_pay_card
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
@@ -683,13 +683,15 @@ def test_authorize_and_capture_with_apple_pay_card
end
def test_authorize_and_capture_with_android_pay_card
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: '2024',
source: :android_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
@@ -700,13 +702,15 @@ def test_authorize_and_capture_with_android_pay_card
end
def test_authorize_and_capture_with_google_pay_card
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: '2024',
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
@@ -817,9 +821,7 @@ def test_unstore_with_delete_method
def test_successful_update
assert response = @gateway.store(
- credit_card('4111111111111111',
- first_name: 'Old First', last_name: 'Old Last',
- month: 9, year: 2012),
+ credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012),
email: 'old@example.com',
phone: '321-654-0987'
)
@@ -838,9 +840,7 @@ def test_successful_update
assert response = @gateway.update(
customer_vault_id,
- credit_card('5105105105105100',
- first_name: 'New First', last_name: 'New Last',
- month: 10, year: 2014),
+ credit_card('5105105105105100', first_name: 'New First', last_name: 'New Last', month: 10, year: 2014),
email: 'new@example.com',
phone: '987-765-5432'
)
@@ -946,25 +946,31 @@ def test_authorize_with_descriptor
end
def test_authorize_with_travel_data
- assert auth = @gateway.authorize(@amount, @credit_card,
+ assert auth = @gateway.authorize(
+ @amount,
+ @credit_card,
travel_data: {
travel_package: 'flight',
departure_date: '2050-07-22',
lodging_check_in_date: '2050-07-22',
lodging_check_out_date: '2050-07-25',
lodging_name: 'Best Hotel Ever'
- })
+ }
+ )
assert_success auth
end
def test_authorize_with_lodging_data
- assert auth = @gateway.authorize(@amount, @credit_card,
+ assert auth = @gateway.authorize(
+ @amount,
+ @credit_card,
lodging_data: {
folio_number: 'ABC123',
check_in_date: '2050-12-22',
check_out_date: '2050-12-25',
room_rate: '80.00'
- })
+ }
+ )
assert_success auth
end
diff --git a/test/remote/gateways/remote_card_stream_test.rb b/test/remote/gateways/remote_card_stream_test.rb
index 046ee4e4d3a..48fe71952ec 100644
--- a/test/remote/gateways/remote_card_stream_test.rb
+++ b/test/remote/gateways/remote_card_stream_test.rb
@@ -6,33 +6,43 @@ def setup
@gateway = CardStreamGateway.new(fixtures(:card_stream))
- @amex = credit_card('374245455400001',
+ @amex = credit_card(
+ '374245455400001',
month: '12',
year: Time.now.year + 1,
verification_value: '4887',
- brand: :american_express)
+ brand: :american_express
+ )
- @mastercard = credit_card('5301250070000191',
+ @mastercard = credit_card(
+ '5301250070000191',
month: '12',
year: Time.now.year + 1,
verification_value: '419',
- brand: :master)
+ brand: :master
+ )
- @visacreditcard = credit_card('4929421234600821',
+ @visacreditcard = credit_card(
+ '4929421234600821',
month: '12',
year: Time.now.year + 1,
verification_value: '356',
- brand: :visa)
+ brand: :visa
+ )
- @visadebitcard = credit_card('4539791001730106',
+ @visadebitcard = credit_card(
+ '4539791001730106',
month: '12',
year: Time.now.year + 1,
verification_value: '289',
- brand: :visa)
+ brand: :visa
+ )
- @declined_card = credit_card('4000300011112220',
+ @declined_card = credit_card(
+ '4000300011112220',
month: '9',
- year: Time.now.year + 1)
+ year: Time.now.year + 1
+ )
@amex_options = {
billing_address: {
@@ -109,10 +119,12 @@ def setup
ip: '1.1.1.1'
}
- @three_ds_enrolled_card = credit_card('4012001037141112',
+ @three_ds_enrolled_card = credit_card(
+ '4012001037141112',
month: '12',
year: '2020',
- brand: :visa)
+ brand: :visa
+ )
@visacredit_three_ds_options = {
threeds_required: true,
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index d03e48b4748..e8b1d0c1e90 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -16,52 +16,64 @@ def setup
@threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: Time.now.year + 1)
@mada_card = credit_card('5043000000000000', brand: 'mada')
- @vts_network_token = network_tokenization_credit_card('4242424242424242',
+ @vts_network_token = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: Time.now.year + 1,
- source: :network_token,
- brand: 'visa',
- verification_value: nil)
-
- @mdes_network_token = network_tokenization_credit_card('5436031030606378',
- eci: '02',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :network_token,
+ brand: 'visa',
+ verification_value: nil
+ )
+
+ @mdes_network_token = network_tokenization_credit_card(
+ '5436031030606378',
+ eci: '02',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: Time.now.year + 1,
- source: :network_token,
- brand: 'master',
- verification_value: nil)
-
- @google_pay_visa_cryptogram_3ds_network_token = network_tokenization_credit_card('4242424242424242',
- eci: '05',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :network_token,
+ brand: 'master',
+ verification_value: nil
+ )
+
+ @google_pay_visa_cryptogram_3ds_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: Time.now.year + 1,
- source: :google_pay,
- verification_value: nil)
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: nil
+ )
- @google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card('5436031030606378',
+ @google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card(
+ '5436031030606378',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: Time.now.year + 1,
- source: :google_pay,
- brand: 'master',
- verification_value: nil)
-
- @google_pay_pan_only_network_token = network_tokenization_credit_card('4242424242424242',
- month: '10',
- year: Time.now.year + 1,
- source: :google_pay,
- verification_value: nil)
-
- @apple_pay_network_token = network_tokenization_credit_card('4242424242424242',
- eci: '05',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ brand: 'master',
+ verification_value: nil
+ )
+
+ @google_pay_pan_only_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: nil
+ )
+
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: Time.now.year + 1,
- source: :apple_pay,
- verification_value: nil)
+ month: '10',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: nil
+ )
@options = {
order_id: '1',
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index 3bd4f9c4750..c49ad1ebdaa 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -10,27 +10,33 @@ def setup
@amount = 1204
@credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123', first_name: 'John', last_name: 'Doe')
- @google_pay = network_tokenization_credit_card('4005550000000019',
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :google_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @apple_pay = network_tokenization_credit_card('4005550000000019',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @declined_apple_pay = network_tokenization_credit_card('4000300011112220',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @declined_apple_pay = network_tokenization_credit_card(
+ '4000300011112220',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
@master_card = credit_card('5454545454545454', brand: 'master')
@options = {}
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
index 46abe4baa0e..30b2dddab3f 100644
--- a/test/remote/gateways/remote_credorax_test.rb
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -45,7 +45,8 @@ def setup
}
}
- @apple_pay_card = network_tokenization_credit_card('4176661000001015',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
month: 10,
year: Time.new.year + 2,
first_name: 'John',
@@ -54,20 +55,25 @@ def setup
payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_card = network_tokenization_credit_card('4176661000001015',
+ @google_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '07')
+ eci: '07'
+ )
- @nt_credit_card = network_tokenization_credit_card('4176661000001015',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4176661000001015',
brand: 'visa',
source: :network_token,
- payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA=')
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA='
+ )
end
def test_successful_purchase_with_apple_pay
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
index c0405751bfc..bca3e7499e7 100644
--- a/test/remote/gateways/remote_cyber_source_rest_test.rb
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -8,10 +8,7 @@ def setup
@bank_account = check(account_number: '4100', routing_number: '121042882')
@declined_bank_account = check(account_number: '550111', routing_number: '121107882')
- @visa_card = credit_card('4111111111111111',
- verification_value: '987',
- month: 12,
- year: 2031)
+ @visa_card = credit_card('4111111111111111', verification_value: '987', month: 12, year: 2031)
@master_card = credit_card('2222420000001113', brand: 'master')
@discover_card = credit_card('6011111111111117', brand: 'discover')
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 0ba41b55000..40277267975 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -10,37 +10,49 @@ def setup
@credit_card = credit_card('4111111111111111', verification_value: '987')
@declined_card = credit_card('801111111111111')
- @master_credit_card = credit_card('5555555555554444',
+ @master_credit_card = credit_card(
+ '5555555555554444',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :master)
+ brand: :master
+ )
@pinless_debit_card = credit_card('4002269999999999')
- @elo_credit_card = credit_card('5067310000000010',
+ @elo_credit_card = credit_card(
+ '5067310000000010',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :elo)
- @three_ds_unenrolled_card = credit_card('4000000000000051',
+ brand: :elo
+ )
+ @three_ds_unenrolled_card = credit_card(
+ '4000000000000051',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :visa)
- @three_ds_enrolled_card = credit_card('4000000000000002',
+ brand: :visa
+ )
+ @three_ds_enrolled_card = credit_card(
+ '4000000000000002',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :visa)
- @three_ds_invalid_card = credit_card('4000000000000010',
+ brand: :visa
+ )
+ @three_ds_invalid_card = credit_card(
+ '4000000000000010',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :visa)
- @three_ds_enrolled_mastercard = credit_card('5200000000001005',
+ brand: :visa
+ )
+ @three_ds_enrolled_mastercard = credit_card(
+ '5200000000001005',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :master)
+ brand: :master
+ )
@amount = 100
@@ -106,10 +118,12 @@ def test_transcript_scrubbing
end
def test_network_tokenization_transcript_scrubbing
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
transcript = capture_transcript(@gateway) do
@gateway.authorize(@amount, credit_card, @options)
@@ -667,10 +681,12 @@ def test_successful_refund_with_bank_account_follow_on
end
def test_network_tokenization_authorize_and_capture
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_successful_response(auth)
@@ -680,10 +696,12 @@ def test_network_tokenization_authorize_and_capture
end
def test_network_tokenization_with_amex_cc_and_basic_cryptogram
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_successful_response(auth)
@@ -696,10 +714,12 @@ def test_network_tokenization_with_amex_cc_longer_cryptogram
# Generate a random 40 bytes binary amex cryptogram => Base64.encode64(Random.bytes(40))
long_cryptogram = "NZwc40C4eTDWHVDXPekFaKkNYGk26w+GYDZmU50cATbjqOpNxR/eYA==\n"
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
eci: '05',
- payment_cryptogram: long_cryptogram)
+ payment_cryptogram: long_cryptogram
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_successful_response(auth)
@@ -709,10 +729,12 @@ def test_network_tokenization_with_amex_cc_longer_cryptogram
end
def test_purchase_with_network_tokenization_with_amex_cc
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
assert auth = @gateway.purchase(@amount, credit_card, @options)
assert_successful_response(auth)
@@ -910,8 +932,11 @@ def test_successful_update_subscription_billing_address
assert response = @gateway.store(@credit_card, @subscription_options)
assert_successful_response(response)
- assert response = @gateway.update(response.authorization, nil,
- { order_id: generate_unique_id, setup_fee: 100, billing_address: address, email: 'someguy1232@fakeemail.net' })
+ assert response = @gateway.update(
+ response.authorization,
+ nil,
+ { order_id: generate_unique_id, setup_fee: 100, billing_address: address, email: 'someguy1232@fakeemail.net' }
+ )
assert_successful_response(response)
end
diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb
index bd03a545a83..89b516dd9d3 100644
--- a/test/remote/gateways/remote_d_local_test.rb
+++ b/test/remote/gateways/remote_d_local_test.rb
@@ -60,16 +60,14 @@ def test_successful_purchase_with_save_option
end
def test_successful_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_match 'The payment was paid', response.message
end
def test_successful_purchase_with_network_tokens_and_store_credential_type
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
response = @gateway.purchase(@amount, credit_card, @options.merge!(stored_credential_type: 'SUBSCRIPTION'))
assert_success response
assert_match 'SUBSCRIPTION', response.params['card']['stored_credential_type']
@@ -78,8 +76,7 @@ def test_successful_purchase_with_network_tokens_and_store_credential_type
def test_successful_purchase_with_network_tokens_and_store_credential_usage
options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
response = @gateway.purchase(@amount, credit_card, options)
assert_success response
assert_match 'USED', response.params['card']['stored_credential_usage']
@@ -209,8 +206,10 @@ def test_failed_purchase
end
def test_failed_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA='
+ )
response = @gateway.purchase(@amount, credit_card, @options.merge(description: '300'))
assert_failure response
assert_match 'The payment was rejected', response.message
diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb
index 7c90ff55646..649e014bb81 100644
--- a/test/remote/gateways/remote_element_test.rb
+++ b/test/remote/gateways/remote_element_test.rb
@@ -13,7 +13,8 @@ def setup
description: 'Store Purchase'
}
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
month: '01',
year: Time.new.year + 2,
first_name: 'Jane',
@@ -22,9 +23,11 @@ def setup
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '05',
transaction_id: '123456789',
- source: :google_pay)
+ source: :google_pay
+ )
- @apple_pay_network_token = network_tokenization_credit_card('4895370015293175',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
month: '10',
year: Time.new.year + 2,
first_name: 'John',
@@ -33,7 +36,8 @@ def setup
payment_cryptogram: 'CeABBJQ1AgAAAAAgJDUCAAAAAAA=',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
end
def test_successful_purchase
diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb
index 2778f1d5c4e..3113dec205d 100644
--- a/test/remote/gateways/remote_eway_rapid_test.rb
+++ b/test/remote/gateways/remote_eway_rapid_test.rb
@@ -112,7 +112,9 @@ def test_successful_purchase_with_shipping_address
end
def test_fully_loaded_purchase
- response = @gateway.purchase(@amount, @credit_card,
+ response = @gateway.purchase(
+ @amount,
+ @credit_card,
redirect_url: 'http://awesomesauce.com',
ip: '0.0.0.0',
application_id: 'Woohoo',
@@ -148,7 +150,8 @@ def test_fully_loaded_purchase
country: 'US',
phone: '1115555555',
fax: '1115556666'
- })
+ }
+ )
assert_success response
end
diff --git a/test/remote/gateways/remote_eway_test.rb b/test/remote/gateways/remote_eway_test.rb
index 64f536d176f..4c306709656 100644
--- a/test/remote/gateways/remote_eway_test.rb
+++ b/test/remote/gateways/remote_eway_test.rb
@@ -4,9 +4,7 @@ class EwayTest < Test::Unit::TestCase
def setup
@gateway = EwayGateway.new(fixtures(:eway))
@credit_card_success = credit_card('4444333322221111')
- @credit_card_fail = credit_card('1234567812345678',
- month: Time.now.month,
- year: Time.now.year - 1)
+ @credit_card_fail = credit_card('1234567812345678', month: Time.now.month, year: Time.now.year - 1)
@params = {
order_id: '1230123',
diff --git a/test/remote/gateways/remote_firstdata_e4_test.rb b/test/remote/gateways/remote_firstdata_e4_test.rb
index 9666a8637d7..e8a84ac81da 100755
--- a/test/remote/gateways/remote_firstdata_e4_test.rb
+++ b/test/remote/gateways/remote_firstdata_e4_test.rb
@@ -26,9 +26,11 @@ def test_successful_purchase
end
def test_successful_purchase_with_network_tokenization
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction Normal - Approved', response.message
diff --git a/test/remote/gateways/remote_firstdata_e4_v27_test.rb b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
index 8b41dc9013a..ef3f1ddb6ea 100644
--- a/test/remote/gateways/remote_firstdata_e4_v27_test.rb
+++ b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
@@ -27,9 +27,11 @@ def test_successful_purchase
end
def test_successful_purchase_with_network_tokenization
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction Normal - Approved', response.message
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index 3a23595dd86..e37e835a010 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -16,26 +16,32 @@ def setup
@cabal_card = credit_card('6271701225979642', brand: 'cabal')
@declined_card = credit_card('5424180279791732')
@preprod_card = credit_card('4111111111111111')
- @apple_pay = network_tokenization_credit_card('4567350000427977',
+ @apple_pay = network_tokenization_credit_card(
+ '4567350000427977',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
first_name: 'John',
last_name: 'Smith',
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay = network_tokenization_credit_card('4567350000427977',
+ @google_pay = network_tokenization_credit_card(
+ '4567350000427977',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
- @google_pay_pan_only = credit_card('4567350000427977',
+ @google_pay_pan_only = credit_card(
+ '4567350000427977',
month: '01',
- year: Time.new.year + 2)
+ year: Time.new.year + 2
+ )
@accepted_amount = 4005
@rejected_amount = 2997
diff --git a/test/remote/gateways/remote_hps_test.rb b/test/remote/gateways/remote_hps_test.rb
index 4b8f7229bfc..9f7a0e08c24 100644
--- a/test/remote/gateways/remote_hps_test.rb
+++ b/test/remote/gateways/remote_hps_test.rb
@@ -359,11 +359,13 @@ def test_transcript_scrubbing
end
def test_transcript_scrubbing_with_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, credit_card, @options)
end
@@ -384,126 +386,150 @@ def test_account_number_scrubbing
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
diff --git a/test/remote/gateways/remote_linkpoint_test.rb b/test/remote/gateways/remote_linkpoint_test.rb
index bb6db34f34f..efcffa07dec 100644
--- a/test/remote/gateways/remote_linkpoint_test.rb
+++ b/test/remote/gateways/remote_linkpoint_test.rb
@@ -100,12 +100,15 @@ def test_successfull_purchase_with_item_entity
end
def test_successful_recurring_payment
- assert response = @gateway.recurring(2400, @credit_card,
+ assert response = @gateway.recurring(
+ 2400,
+ @credit_card,
order_id: generate_unique_id,
installments: 12,
startdate: 'immediate',
periodicity: :monthly,
- billing_address: address)
+ billing_address: address
+ )
assert_success response
assert_equal 'APPROVED', response.params['approved']
diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb
index 4cff45413cf..80241a8b361 100644
--- a/test/remote/gateways/remote_litle_test.rb
+++ b/test/remote/gateways/remote_litle_test.rb
@@ -160,7 +160,9 @@ def test__cvv_result
end
def test_unsuccessful_authorization
- assert response = @gateway.authorize(60060, @declined_card,
+ assert response = @gateway.authorize(
+ 60060,
+ @declined_card,
{
order_id: '6',
billing_address: {
@@ -171,7 +173,8 @@ def test_unsuccessful_authorization
zip: '03038',
country: 'US'
}
- })
+ }
+ )
assert_failure response
assert_equal 'Insufficient Funds', response.message
end
diff --git a/test/remote/gateways/remote_mercado_pago_test.rb b/test/remote/gateways/remote_mercado_pago_test.rb
index b9a9c0d5f3e..9aab14911f3 100644
--- a/test/remote/gateways/remote_mercado_pago_test.rb
+++ b/test/remote/gateways/remote_mercado_pago_test.rb
@@ -10,26 +10,31 @@ def setup
@amount = 500
@credit_card = credit_card('5031433215406351')
@colombian_card = credit_card('4013540682746260')
- @elo_credit_card = credit_card('5067268650517446',
+ @elo_credit_card = credit_card(
+ '5067268650517446',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @cabal_credit_card = credit_card('6035227716427021',
+ verification_value: '737'
+ )
+ @cabal_credit_card = credit_card(
+ '6035227716427021',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @naranja_credit_card = credit_card('5895627823453005',
+ verification_value: '737'
+ )
+ @naranja_credit_card = credit_card(
+ '5895627823453005',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '123')
- @declined_card = credit_card('5031433215406351',
- first_name: 'OTHE')
+ verification_value: '123'
+ )
+ @declined_card = credit_card('5031433215406351', first_name: 'OTHE')
@options = {
billing_address: address,
shipping_address: address,
diff --git a/test/remote/gateways/remote_merchant_ware_version_four_test.rb b/test/remote/gateways/remote_merchant_ware_version_four_test.rb
index 2d18cbeb868..b553611b7e3 100644
--- a/test/remote/gateways/remote_merchant_ware_version_four_test.rb
+++ b/test/remote/gateways/remote_merchant_ware_version_four_test.rb
@@ -69,9 +69,11 @@ def test_purchase_and_reference_purchase
assert_success purchase
assert purchase.authorization
- assert reference_purchase = @gateway.purchase(@amount,
+ assert reference_purchase = @gateway.purchase(
+ @amount,
purchase.authorization,
- @reference_purchase_options)
+ @reference_purchase_options
+ )
assert_success reference_purchase
assert_not_nil reference_purchase.authorization
end
diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb
index c63de177909..05b7bf1208d 100644
--- a/test/remote/gateways/remote_moneris_test.rb
+++ b/test/remote/gateways/remote_moneris_test.rb
@@ -41,7 +41,9 @@ def test_successful_purchase
def test_successful_cavv_purchase
# See https://developer.moneris.com/livedemo/3ds2/cavv_purchase/tool/php
- assert response = @gateway.purchase(@amount, @visa_credit_card_3ds,
+ assert response = @gateway.purchase(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -50,7 +52,8 @@ def test_successful_cavv_purchase
three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -230,7 +233,9 @@ def test_successful_authorization_and_void
def test_successful_cavv_authorization
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -239,7 +244,8 @@ def test_successful_cavv_authorization
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -248,7 +254,9 @@ def test_successful_cavv_authorization
def test_successful_cavv_authorization_and_capture
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -257,7 +265,8 @@ def test_successful_cavv_authorization_and_capture
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -270,7 +279,9 @@ def test_failed_cavv_authorization
omit('There is no way to currently create a failed cavv authorization scenario')
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@fail_amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @fail_amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -279,7 +290,8 @@ def test_failed_cavv_authorization
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_failure response
end
diff --git a/test/remote/gateways/remote_net_registry_test.rb b/test/remote/gateways/remote_net_registry_test.rb
index 2b983e8b680..dfbb80c9ac1 100644
--- a/test/remote/gateways/remote_net_registry_test.rb
+++ b/test/remote/gateways/remote_net_registry_test.rb
@@ -56,9 +56,7 @@ def test_successful_authorization_and_capture
assert_equal 'approved', response.params['status']
assert_match(/\A\d{6}\z/, response.authorization)
- response = @gateway.capture(@amount,
- response.authorization,
- credit_card: @valid_creditcard)
+ response = @gateway.capture(@amount, response.authorization, credit_card: @valid_creditcard)
assert_success response
assert_equal 'approved', response.params['status']
end
diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb
index 3ba0732241c..7814d39dd92 100644
--- a/test/remote/gateways/remote_nmi_test.rb
+++ b/test/remote/gateways/remote_nmi_test.rb
@@ -10,13 +10,15 @@ def setup
routing_number: '123123123',
account_number: '123123123'
)
- @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: '2024',
source: :apple_pay,
eci: '5',
- transaction_id: '123456789')
+ transaction_id: '123456789'
+ )
@options = {
order_id: generate_unique_id,
billing_address: address,
diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb
index 5152ab4c338..398e33b53c4 100644
--- a/test/remote/gateways/remote_orbital_test.rb
+++ b/test/remote/gateways/remote_orbital_test.rb
@@ -195,11 +195,13 @@ def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci
end
def test_successful_purchase_with_master_card_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'master')
+ brand: 'master'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -249,11 +251,13 @@ def test_successful_purchase_with_sca_merchant_initiated_master_card
end
def test_successful_purchase_with_american_express_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'american_express')
+ brand: 'american_express'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -261,11 +265,13 @@ def test_successful_purchase_with_american_express_network_tokenization_credit_c
end
def test_successful_purchase_with_discover_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'discover')
+ brand: 'discover'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -1226,11 +1232,13 @@ def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci
end
def test_successful_purchase_with_master_card_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'master')
+ brand: 'master'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -1238,11 +1246,13 @@ def test_successful_purchase_with_master_card_network_tokenization_credit_card
end
def test_successful_purchase_with_american_express_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'american_express')
+ brand: 'american_express'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -1250,11 +1260,13 @@ def test_successful_purchase_with_american_express_network_tokenization_credit_c
end
def test_successful_purchase_with_discover_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'discover')
+ brand: 'discover'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb
index b5a0292cd25..280e89e3883 100644
--- a/test/remote/gateways/remote_pay_junction_test.rb
+++ b/test/remote/gateways/remote_pay_junction_test.rb
@@ -71,8 +71,7 @@ def test_successful_capture
response = @gateway.capture(AMOUNT, auth.authorization, @options)
assert_success response
assert_equal 'capture', response.params['posture'], 'Should be a capture'
- assert_equal auth.authorization, response.authorization,
- 'Should maintain transaction ID across request'
+ assert_equal auth.authorization, response.authorization, 'Should maintain transaction ID across request'
end
def test_successful_credit
@@ -93,8 +92,7 @@ def test_successful_void
assert response = @gateway.void(purchase.authorization, order_id: order_id)
assert_success response
assert_equal 'void', response.params['posture'], 'Should be a capture'
- assert_equal purchase.authorization, response.authorization,
- 'Should maintain transaction ID across request'
+ assert_equal purchase.authorization, response.authorization, 'Should maintain transaction ID across request'
end
def test_successful_instant_purchase
@@ -110,17 +108,19 @@ def test_successful_instant_purchase
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
assert_equal 'capture', response.params['posture'], 'Should be captured funds'
assert_equal 'charge', response.params['transaction_action']
- assert_not_equal purchase.authorization, response.authorization,
- 'Should have recieved new transaction ID'
+ assert_not_equal purchase.authorization, response.authorization, 'Should have recieved new transaction ID'
assert_success response
end
def test_successful_recurring
- assert response = @gateway.recurring(AMOUNT, @credit_card,
+ assert response = @gateway.recurring(
+ AMOUNT,
+ @credit_card,
periodicity: :monthly,
payments: 12,
- order_id: generate_unique_id[0..15])
+ order_id: generate_unique_id[0..15]
+ )
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
assert_equal 'charge', response.params['transaction_action']
diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb
index 71823d5d617..ed12025cd79 100644
--- a/test/remote/gateways/remote_payflow_test.rb
+++ b/test/remote/gateways/remote_payflow_test.rb
@@ -445,12 +445,15 @@ def test_reference_purchase
def test_recurring_with_initial_authorization
response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(1000, @credit_card,
+ @gateway.recurring(
+ 1000,
+ @credit_card,
periodicity: :monthly,
initial_transaction: {
type: :purchase,
amount: 500
- })
+ }
+ )
end
assert_success response
diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb
index 82d7db2bbb9..910b16c262d 100644
--- a/test/remote/gateways/remote_paymentez_test.rb
+++ b/test/remote/gateways/remote_paymentez_test.rb
@@ -8,13 +8,15 @@ def setup
@amount = 100
@credit_card = credit_card('4111111111111111', verification_value: '666')
@otp_card = credit_card('36417002140808', verification_value: '666')
- @elo_credit_card = credit_card('6362970000457013',
+ @elo_credit_card = credit_card(
+ '6362970000457013',
month: 10,
year: 2022,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
@declined_card = credit_card('4242424242424242', verification_value: '666')
@options = {
billing_address: address,
diff --git a/test/remote/gateways/remote_paypal_test.rb b/test/remote/gateways/remote_paypal_test.rb
index d73c21528c8..eee5d7aba1a 100644
--- a/test/remote/gateways/remote_paypal_test.rb
+++ b/test/remote/gateways/remote_paypal_test.rb
@@ -232,9 +232,11 @@ def test_successful_multiple_transfer
response = @gateway.purchase(900, @credit_card, @params)
assert_success response
- response = @gateway.transfer([@amount, 'joe@example.com'],
+ response = @gateway.transfer(
+ [@amount, 'joe@example.com'],
[600, 'jane@example.com', { note: 'Thanks for taking care of that' }],
- subject: 'Your money')
+ subject: 'Your money'
+ )
assert_success response
end
diff --git a/test/remote/gateways/remote_payway_test.rb b/test/remote/gateways/remote_payway_test.rb
index 5276e60e38e..643411f520a 100644
--- a/test/remote/gateways/remote_payway_test.rb
+++ b/test/remote/gateways/remote_payway_test.rb
@@ -8,37 +8,49 @@ def setup
@gateway = ActiveMerchant::Billing::PaywayGateway.new(fixtures(:payway))
- @visa = credit_card('4564710000000004',
+ @visa = credit_card(
+ '4564710000000004',
month: 2,
year: 2019,
- verification_value: '847')
+ verification_value: '847'
+ )
- @mastercard = credit_card('5163200000000008',
+ @mastercard = credit_card(
+ '5163200000000008',
month: 8,
year: 2020,
verification_value: '070',
- brand: 'master')
+ brand: 'master'
+ )
- @expired = credit_card('4564710000000012',
+ @expired = credit_card(
+ '4564710000000012',
month: 2,
year: 2005,
- verification_value: '963')
+ verification_value: '963'
+ )
- @low = credit_card('4564710000000020',
+ @low = credit_card(
+ '4564710000000020',
month: 5,
year: 2020,
- verification_value: '234')
+ verification_value: '234'
+ )
- @stolen_mastercard = credit_card('5163200000000016',
+ @stolen_mastercard = credit_card(
+ '5163200000000016',
month: 12,
year: 2019,
verification_value: '728',
- brand: 'master')
+ brand: 'master'
+ )
- @invalid = credit_card('4564720000000037',
+ @invalid = credit_card(
+ '4564720000000037',
month: 9,
year: 2019,
- verification_value: '030')
+ verification_value: '030'
+ )
end
def test_successful_visa
diff --git a/test/remote/gateways/remote_psl_card_test.rb b/test/remote/gateways/remote_psl_card_test.rb
index 44630800f89..18e911faea2 100644
--- a/test/remote/gateways/remote_psl_card_test.rb
+++ b/test/remote/gateways/remote_psl_card_test.rb
@@ -24,65 +24,55 @@ def setup
end
def test_successful_visa_purchase
- response = @gateway.purchase(@accept_amount, @visa,
- billing_address: @visa_address)
+ response = @gateway.purchase(@accept_amount, @visa, billing_address: @visa_address)
assert_success response
assert response.test?
end
def test_successful_visa_debit_purchase
- response = @gateway.purchase(@accept_amount, @visa_debit,
- billing_address: @visa_debit_address)
+ response = @gateway.purchase(@accept_amount, @visa_debit, billing_address: @visa_debit_address)
assert_success response
end
# Fix regression discovered in production
def test_visa_debit_purchase_should_not_send_debit_info_if_present
@visa_debit.start_month = '07'
- response = @gateway.purchase(@accept_amount, @visa_debit,
- billing_address: @visa_debit_address)
+ response = @gateway.purchase(@accept_amount, @visa_debit, billing_address: @visa_debit_address)
assert_success response
end
def test_successful_visa_purchase_specifying_currency
- response = @gateway.purchase(@accept_amount, @visa,
- billing_address: @visa_address,
- currency: 'GBP')
+ response = @gateway.purchase(@accept_amount, @visa, billing_address: @visa_address, currency: 'GBP')
assert_success response
assert response.test?
end
def test_successful_solo_purchase
- response = @gateway.purchase(@accept_amount, @solo,
- billing_address: @solo_address)
+ response = @gateway.purchase(@accept_amount, @solo, billing_address: @solo_address)
assert_success response
assert response.test?
end
def test_referred_purchase
- response = @gateway.purchase(@referred_amount, @uk_maestro,
- billing_address: @uk_maestro_address)
+ response = @gateway.purchase(@referred_amount, @uk_maestro, billing_address: @uk_maestro_address)
assert_failure response
assert response.test?
end
def test_declined_purchase
- response = @gateway.purchase(@declined_amount, @uk_maestro,
- billing_address: @uk_maestro_address)
+ response = @gateway.purchase(@declined_amount, @uk_maestro, billing_address: @uk_maestro_address)
assert_failure response
assert response.test?
end
def test_declined_keep_card_purchase
- response = @gateway.purchase(@keep_card_amount, @uk_maestro,
- billing_address: @uk_maestro_address)
+ response = @gateway.purchase(@keep_card_amount, @uk_maestro, billing_address: @uk_maestro_address)
assert_failure response
assert response.test?
end
def test_successful_authorization
- response = @gateway.authorize(@accept_amount, @visa,
- billing_address: @visa_address)
+ response = @gateway.authorize(@accept_amount, @visa, billing_address: @visa_address)
assert_success response
assert response.test?
end
@@ -91,15 +81,13 @@ def test_no_login
@gateway = PslCardGateway.new(
login: ''
)
- response = @gateway.authorize(@accept_amount, @uk_maestro,
- billing_address: @uk_maestro_address)
+ response = @gateway.authorize(@accept_amount, @uk_maestro, billing_address: @uk_maestro_address)
assert_failure response
assert response.test?
end
def test_successful_authorization_and_capture
- authorization = @gateway.authorize(@accept_amount, @visa,
- billing_address: @visa_address)
+ authorization = @gateway.authorize(@accept_amount, @visa, billing_address: @visa_address)
assert_success authorization
assert authorization.test?
diff --git a/test/remote/gateways/remote_realex_test.rb b/test/remote/gateways/remote_realex_test.rb
index 39006c27d44..a45904d8e60 100644
--- a/test/remote/gateways/remote_realex_test.rb
+++ b/test/remote/gateways/remote_realex_test.rb
@@ -18,17 +18,21 @@ def setup
@mastercard_referral_a = card_fixtures(:realex_mastercard_referral_a)
@mastercard_coms_error = card_fixtures(:realex_mastercard_coms_error)
- @apple_pay = network_tokenization_credit_card('4242424242424242',
+ @apple_pay = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @declined_apple_pay = network_tokenization_credit_card('4000120000001154',
+ @declined_apple_pay = network_tokenization_credit_card(
+ '4000120000001154',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
@amount = 10000
end
@@ -38,13 +42,16 @@ def card_fixtures(name)
def test_realex_purchase
[@visa, @mastercard].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil response
assert_success response
assert response.test?
@@ -58,9 +65,12 @@ def test_realex_purchase_with_invalid_login
login: 'invalid',
password: 'invalid'
)
- response = gateway.purchase(@amount, @visa,
+ response = gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Invalid login test')
+ description: 'Invalid login test'
+ )
assert_not_nil response
assert_failure response
@@ -70,9 +80,12 @@ def test_realex_purchase_with_invalid_login
end
def test_realex_purchase_with_invalid_account
- response = RealexGateway.new(fixtures(:realex_with_account).merge(account: 'invalid')).purchase(@amount, @visa,
+ response = RealexGateway.new(fixtures(:realex_with_account).merge(account: 'invalid')).purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex purchase with invalid account')
+ description: 'Test Realex purchase with invalid account'
+ )
assert_not_nil response
assert_failure response
@@ -90,9 +103,12 @@ def test_realex_purchase_with_apple_pay
def test_realex_purchase_declined
[@visa_declined, @mastercard_declined].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex purchase declined')
+ description: 'Test Realex purchase declined'
+ )
assert_not_nil response
assert_failure response
@@ -178,9 +194,12 @@ def test_subsequent_purchase_with_stored_credential
def test_realex_purchase_referral_b
[@visa_referral_b, @mastercard_referral_b].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex Referral B')
+ description: 'Test Realex Referral B'
+ )
assert_not_nil response
assert_failure response
assert response.test?
@@ -191,9 +210,12 @@ def test_realex_purchase_referral_b
def test_realex_purchase_referral_a
[@visa_referral_a, @mastercard_referral_a].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex Rqeferral A')
+ description: 'Test Realex Rqeferral A'
+ )
assert_not_nil response
assert_failure response
assert_equal '103', response.params['result']
@@ -203,9 +225,12 @@ def test_realex_purchase_referral_a
def test_realex_purchase_coms_error
[@visa_coms_error, @mastercard_coms_error].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex coms error')
+ description: 'Test Realex coms error'
+ )
assert_not_nil response
assert_failure response
@@ -217,9 +242,12 @@ def test_realex_purchase_coms_error
def test_realex_expiry_month_error
@visa.month = 13
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex expiry month error')
+ description: 'Test Realex expiry month error'
+ )
assert_not_nil response
assert_failure response
@@ -230,9 +258,12 @@ def test_realex_expiry_month_error
def test_realex_expiry_year_error
@visa.year = 2005
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex expiry year error')
+ description: 'Test Realex expiry year error'
+ )
assert_not_nil response
assert_failure response
@@ -244,9 +275,12 @@ def test_invalid_credit_card_name
@visa.first_name = ''
@visa.last_name = ''
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'test_chname_error')
+ description: 'test_chname_error'
+ )
assert_not_nil response
assert_failure response
@@ -257,32 +291,41 @@ def test_invalid_credit_card_name
def test_cvn
@visa_cvn = @visa.clone
@visa_cvn.verification_value = '111'
- response = @gateway.purchase(@amount, @visa_cvn,
+ response = @gateway.purchase(
+ @amount,
+ @visa_cvn,
order_id: generate_unique_id,
- description: 'test_cvn')
+ description: 'test_cvn'
+ )
assert_not_nil response
assert_success response
assert response.authorization.length > 0
end
def test_customer_number
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'test_cust_num',
- customer: 'my customer id')
+ customer: 'my customer id'
+ )
assert_not_nil response
assert_success response
assert response.authorization.length > 0
end
def test_realex_authorize
- response = @gateway.authorize(@amount, @visa,
+ response = @gateway.authorize(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil response
assert_success response
@@ -294,13 +337,16 @@ def test_realex_authorize
def test_realex_authorize_then_capture
order_id = generate_unique_id
- auth_response = @gateway.authorize(@amount, @visa,
+ auth_response = @gateway.authorize(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert auth_response.test?
capture_response = @gateway.capture(nil, auth_response.authorization)
@@ -315,13 +361,16 @@ def test_realex_authorize_then_capture
def test_realex_authorize_then_capture_with_extra_amount
order_id = generate_unique_id
- auth_response = @gateway.authorize(@amount * 115, @visa,
+ auth_response = @gateway.authorize(
+ @amount * 115,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert auth_response.test?
capture_response = @gateway.capture(@amount, auth_response.authorization)
@@ -336,13 +385,16 @@ def test_realex_authorize_then_capture_with_extra_amount
def test_realex_purchase_then_void
order_id = generate_unique_id
- purchase_response = @gateway.purchase(@amount, @visa,
+ purchase_response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert purchase_response.test?
void_response = @gateway.void(purchase_response.authorization)
@@ -358,13 +410,16 @@ def test_realex_purchase_then_refund
gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(rebate_secret: 'rebate'))
- purchase_response = gateway_with_refund_password.purchase(@amount, @visa,
+ purchase_response = gateway_with_refund_password.purchase(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert purchase_response.test?
rebate_response = gateway_with_refund_password.refund(@amount, purchase_response.authorization)
@@ -376,9 +431,11 @@ def test_realex_purchase_then_refund
end
def test_realex_verify
- response = @gateway.verify(@visa,
+ response = @gateway.verify(
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex verify')
+ description: 'Test Realex verify'
+ )
assert_not_nil response
assert_success response
@@ -388,9 +445,11 @@ def test_realex_verify
end
def test_realex_verify_declined
- response = @gateway.verify(@visa_declined,
+ response = @gateway.verify(
+ @visa_declined,
order_id: generate_unique_id,
- description: 'Test Realex verify declined')
+ description: 'Test Realex verify declined'
+ )
assert_not_nil response
assert_failure response
@@ -402,13 +461,16 @@ def test_realex_verify_declined
def test_successful_credit
gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(refund_secret: 'refund'))
- credit_response = gateway_with_refund_password.credit(@amount, @visa,
+ credit_response = gateway_with_refund_password.credit(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'Test Realex Credit',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil credit_response
assert_success credit_response
@@ -417,13 +479,16 @@ def test_successful_credit
end
def test_failed_credit
- credit_response = @gateway.credit(@amount, @visa,
+ credit_response = @gateway.credit(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'Test Realex Credit',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil credit_response
assert_failure credit_response
@@ -433,13 +498,16 @@ def test_failed_credit
def test_maps_avs_and_cvv_response_codes
[@visa, @mastercard].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil response
assert_success response
assert_equal 'M', response.avs_result['code']
@@ -449,13 +517,16 @@ def test_maps_avs_and_cvv_response_codes
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
- @gateway.purchase(@amount, @visa_declined,
+ @gateway.purchase(
+ @amount,
+ @visa_declined,
order_id: generate_unique_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
end
clean_transcript = @gateway.scrub(transcript)
diff --git a/test/remote/gateways/remote_sage_pay_test.rb b/test/remote/gateways/remote_sage_pay_test.rb
index e8de154a2aa..487332e1097 100644
--- a/test/remote/gateways/remote_sage_pay_test.rb
+++ b/test/remote/gateways/remote_sage_pay_test.rb
@@ -142,10 +142,7 @@ def test_successful_authorization_and_capture_and_refund
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
-
- assert refund = @gateway.refund(@amount, capture.authorization,
- description: 'Crediting trx',
- order_id: generate_unique_id)
+ assert refund = @gateway.refund(@amount, capture.authorization, description: 'Crediting trx', order_id: generate_unique_id)
assert_success refund
end
@@ -168,11 +165,7 @@ def test_successful_purchase_and_void
def test_successful_purchase_and_refund
assert purchase = @gateway.purchase(@amount, @mastercard, @options)
assert_success purchase
-
- assert refund = @gateway.refund(@amount, purchase.authorization,
- description: 'Crediting trx',
- order_id: generate_unique_id)
-
+ assert refund = @gateway.refund(@amount, purchase.authorization, description: 'Crediting trx', order_id: generate_unique_id)
assert_success refund
end
diff --git a/test/remote/gateways/remote_sage_test.rb b/test/remote/gateways/remote_sage_test.rb
index 8ec91e95810..1bbca7cbad1 100644
--- a/test/remote/gateways/remote_sage_test.rb
+++ b/test/remote/gateways/remote_sage_test.rb
@@ -164,8 +164,7 @@ def test_partial_refund
def test_store_visa
assert response = @gateway.store(@visa, @options)
assert_success response
- assert response.authorization,
- 'Store card authorization should not be nil'
+ assert response.authorization, 'Store card authorization should not be nil'
assert_not_nil response.message
end
@@ -176,15 +175,13 @@ def test_failed_store
end
def test_unstore_visa
- assert auth = @gateway.store(@visa, @options).authorization,
- 'Unstore card authorization should not be nil'
+ assert auth = @gateway.store(@visa, @options).authorization, 'Unstore card authorization should not be nil'
assert response = @gateway.unstore(auth, @options)
assert_success response
end
def test_failed_unstore_visa
- assert auth = @gateway.store(@visa, @options).authorization,
- 'Unstore card authorization should not be nil'
+ assert auth = @gateway.store(@visa, @options).authorization, 'Unstore card authorization should not be nil'
assert response = @gateway.unstore(auth, @options)
assert_success response
end
diff --git a/test/remote/gateways/remote_secure_pay_test.rb b/test/remote/gateways/remote_secure_pay_test.rb
index dec1ff43d41..77d41451b3b 100644
--- a/test/remote/gateways/remote_secure_pay_test.rb
+++ b/test/remote/gateways/remote_secure_pay_test.rb
@@ -4,9 +4,7 @@ class RemoteSecurePayTest < Test::Unit::TestCase
def setup
@gateway = SecurePayGateway.new(fixtures(:secure_pay))
- @credit_card = credit_card('4111111111111111',
- month: '7',
- year: '2014')
+ @credit_card = credit_card('4111111111111111', month: '7', year: '2014')
@options = {
order_id: generate_unique_id,
diff --git a/test/remote/gateways/remote_skipjack_test.rb b/test/remote/gateways/remote_skipjack_test.rb
index 096aaee9602..1c53076c8ff 100644
--- a/test/remote/gateways/remote_skipjack_test.rb
+++ b/test/remote/gateways/remote_skipjack_test.rb
@@ -6,8 +6,7 @@ def setup
@gateway = SkipJackGateway.new(fixtures(:skip_jack))
- @credit_card = credit_card('4445999922225',
- verification_value: '999')
+ @credit_card = credit_card('4445999922225', verification_value: '999')
@amount = 100
diff --git a/test/remote/gateways/remote_stripe_android_pay_test.rb b/test/remote/gateways/remote_stripe_android_pay_test.rb
index 6daee95f62b..7adda284d40 100644
--- a/test/remote/gateways/remote_stripe_android_pay_test.rb
+++ b/test/remote/gateways/remote_stripe_android_pay_test.rb
@@ -15,11 +15,13 @@ def setup
end
def test_successful_purchase_with_android_pay_raw_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -30,11 +32,13 @@ def test_successful_purchase_with_android_pay_raw_cryptogram
end
def test_successful_auth_with_android_pay_raw_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
diff --git a/test/remote/gateways/remote_stripe_apple_pay_test.rb b/test/remote/gateways/remote_stripe_apple_pay_test.rb
index f6d844feaba..b1a2f8c92aa 100644
--- a/test/remote/gateways/remote_stripe_apple_pay_test.rb
+++ b/test/remote/gateways/remote_stripe_apple_pay_test.rb
@@ -101,11 +101,13 @@ def test_purchase_with_unsuccessful_apple_pay_token_exchange
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -116,10 +118,12 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -130,11 +134,13 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
end
def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -145,10 +151,12 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
end
def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 04399ed4f22..c2957770232 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -11,30 +11,42 @@ def setup
@three_ds_moto_enabled = 'pm_card_authenticationRequiredOnSetup'
@three_ds_authentication_required = 'pm_card_authenticationRequired'
@three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession'
- @three_ds_off_session_credit_card = credit_card('4000002500003155',
+ @three_ds_off_session_credit_card = credit_card(
+ '4000002500003155',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_1_credit_card = credit_card('4000000000003063',
+ year: 2028
+ )
+ @three_ds_1_credit_card = credit_card(
+ '4000000000003063',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_credit_card = credit_card('4000000000003220',
+ year: 2028
+ )
+ @three_ds_credit_card = credit_card(
+ '4000000000003220',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_not_required_card = credit_card('4000000000003055',
+ year: 2028
+ )
+ @three_ds_not_required_card = credit_card(
+ '4000000000003055',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_external_data_card = credit_card('4000002760003184',
+ year: 2028
+ )
+ @three_ds_external_data_card = credit_card(
+ '4000002760003184',
verification_value: '737',
month: 10,
- year: 2031)
- @visa_card = credit_card('4242424242424242',
+ year: 2031
+ )
+ @visa_card = credit_card(
+ '4242424242424242',
verification_value: '737',
month: 10,
- year: 2028)
+ year: 2028
+ )
@google_pay = network_tokenization_credit_card(
'4242424242424242',
@@ -744,7 +756,7 @@ def test_purchase_works_with_stored_credentials_without_optional_ds_transaction_
confirm: true,
off_session: true,
stored_credential: {
- network_transaction_id: '1098510912210968', # TEST env seems happy with any value :/
+ network_transaction_id: '1098510912210968' # TEST env seems happy with any value :/
}
})
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index d259afdf089..585c554b700 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -9,16 +9,20 @@ def setup
@year = (Time.now.year + 2).to_s[-2..-1].to_i
@credit_card = credit_card('4111111111111111')
@amex_card = credit_card('3714 496353 98431')
- @elo_credit_card = credit_card('4514 1600 0000 0008',
+ @elo_credit_card = credit_card(
+ '4514 1600 0000 0008',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
- @credit_card_with_two_digits_year = credit_card('4111111111111111',
+ brand: 'elo'
+ )
+ @credit_card_with_two_digits_year = credit_card(
+ '4111111111111111',
month: 10,
- year: @year)
+ year: @year
+ )
@cabal_card = credit_card('6035220000000006')
@naranja_card = credit_card('5895620000000002')
@sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
@@ -27,14 +31,18 @@ def setup
@threeDS2_card = credit_card('4111111111111111', first_name: nil, last_name: '3DS_V2_FRICTIONLESS_IDENTIFIED')
@threeDS2_challenge_card = credit_card('4000000000001091', first_name: nil, last_name: 'challenge-me-plz')
@threeDS_card_external_MPI = credit_card('4444333322221111', first_name: 'AA', last_name: 'BD')
- @nt_credit_card = network_tokenization_credit_card('4895370015293175',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4895370015293175',
brand: 'visa',
eci: '07',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @nt_credit_card_without_eci = network_tokenization_credit_card('4895370015293175',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @nt_credit_card_without_eci = network_tokenization_credit_card(
+ '4895370015293175',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@options = {
order_id: generate_unique_id,
@@ -131,7 +139,8 @@ def setup
sub_tax_id: '987-65-4321'
}
}
- @apple_pay_network_token = network_tokenization_credit_card('4895370015293175',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
month: 10,
year: Time.new.year + 2,
first_name: 'John',
@@ -140,15 +149,18 @@ def setup
payment_cryptogram: 'abc1234567890',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
end
def test_successful_purchase
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 27563086990..8c562336cea 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -151,6 +151,7 @@ def formatted_expiration_date(credit_card)
end
def credit_card(number = '4242424242424242', options = {})
+ number = number.is_a?(Integer) ? number.to_s : number
defaults = {
number: number,
month: default_expiration_date.month,
@@ -213,10 +214,12 @@ def apple_pay_payment_token(options = {})
transaction_identifier: 'uniqueidentifier123'
}.update(options)
- ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(
+ defaults[:payment_data],
payment_instrument_name: defaults[:payment_instrument_name],
payment_network: defaults[:payment_network],
- transaction_identifier: defaults[:transaction_identifier])
+ transaction_identifier: defaults[:transaction_identifier]
+ )
end
def address(options = {})
@@ -295,7 +298,7 @@ def fixtures(key)
def load_fixtures
[DEFAULT_CREDENTIALS, LOCAL_CREDENTIALS].inject({}) do |credentials, file_name|
if File.exist?(file_name)
- yaml_data = YAML.safe_load(File.read(file_name), [], [], true)
+ yaml_data = YAML.safe_load(File.read(file_name), aliases: true)
credentials.merge!(symbolize_keys(yaml_data))
end
credentials
diff --git a/test/unit/credit_card_test.rb b/test/unit/credit_card_test.rb
index e592bef4af6..595b3698bfa 100644
--- a/test/unit/credit_card_test.rb
+++ b/test/unit/credit_card_test.rb
@@ -174,7 +174,7 @@ def test_expired_card_should_have_one_error_on_year
end
def test_should_identify_wrong_card_brand
- c = credit_card(brand: 'master')
+ c = credit_card('4779139500118580', brand: 'master')
assert_not_valid c
end
diff --git a/test/unit/fixtures_test.rb b/test/unit/fixtures_test.rb
index b72720d928c..1b99051a5dd 100644
--- a/test/unit/fixtures_test.rb
+++ b/test/unit/fixtures_test.rb
@@ -2,7 +2,7 @@
class FixturesTest < Test::Unit::TestCase
def test_sort
- keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), [], [], true).keys
+ keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), aliases: true).keys
assert_equal(
keys,
keys.sort
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index c06b8ed7c7e..e37bb8accc0 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -12,52 +12,64 @@ def setup
@bank_account = check()
- @credit_card = credit_card('4111111111111111',
+ @credit_card = credit_card(
+ '4111111111111111',
month: 8,
year: 2018,
first_name: 'Test',
last_name: 'Card',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @elo_credit_card = credit_card('5066 9911 1111 1118',
+ @elo_credit_card = credit_card(
+ '5066 9911 1111 1118',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
- @cabal_credit_card = credit_card('6035 2277 1642 7021',
+ @cabal_credit_card = credit_card(
+ '6035 2277 1642 7021',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @unionpay_credit_card = credit_card('8171 9999 0000 0000 021',
+ @unionpay_credit_card = credit_card(
+ '8171 9999 0000 0000 021',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
@three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa)
- @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
month: '08',
year: '2018',
source: :apple_pay,
- verification_value: nil)
+ verification_value: nil
+ )
- @nt_credit_card = network_tokenization_credit_card('4895370015293175',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4895370015293175',
brand: 'visa',
eci: '07',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@amount = 100
diff --git a/test/unit/gateways/authorize_net_arb_test.rb b/test/unit/gateways/authorize_net_arb_test.rb
index 2b32c503d60..dfcc2d4c9ae 100644
--- a/test/unit/gateways/authorize_net_arb_test.rb
+++ b/test/unit/gateways/authorize_net_arb_test.rb
@@ -18,7 +18,9 @@ def setup
def test_successful_recurring
@gateway.expects(:ssl_post).returns(successful_recurring_response)
- response = @gateway.recurring(@amount, @credit_card,
+ response = @gateway.recurring(
+ @amount,
+ @credit_card,
billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'),
interval: {
length: 10,
@@ -27,7 +29,8 @@ def test_successful_recurring
duration: {
start_date: Time.now.strftime('%Y-%m-%d'),
occurrences: 30
- })
+ }
+ )
assert_instance_of Response, response
assert response.success?
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index 343179b37fd..b0f3b957b0e 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -1318,9 +1318,7 @@ def test_dont_include_cust_id_for_phone_numbers
end
def test_includes_shipping_name_when_different_from_billing_name
- card = credit_card('4242424242424242',
- first_name: 'billing',
- last_name: 'name')
+ card = credit_card('4242424242424242', first_name: 'billing', last_name: 'name')
options = {
order_id: 'a' * 21,
@@ -1341,9 +1339,7 @@ def test_includes_shipping_name_when_different_from_billing_name
end
def test_includes_shipping_name_when_passed_as_options
- card = credit_card('4242424242424242',
- first_name: 'billing',
- last_name: 'name')
+ card = credit_card('4242424242424242', first_name: 'billing', last_name: 'name')
shipping_address = address(first_name: 'shipping', last_name: 'lastname')
shipping_address.delete(:name)
@@ -1366,9 +1362,7 @@ def test_includes_shipping_name_when_passed_as_options
end
def test_truncation
- card = credit_card('4242424242424242',
- first_name: 'a' * 51,
- last_name: 'a' * 51)
+ card = credit_card('4242424242424242', first_name: 'a' * 51, last_name: 'a' * 51)
options = {
order_id: 'a' * 21,
@@ -1440,8 +1434,7 @@ def test_supports_scrubbing?
end
def test_successful_apple_pay_authorization_with_network_tokenization
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = stub_comms do
@gateway.authorize(@amount, credit_card)
@@ -1459,8 +1452,7 @@ def test_successful_apple_pay_authorization_with_network_tokenization
end
def test_failed_apple_pay_authorization_with_network_tokenization_not_supported
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = stub_comms do
@gateway.authorize(@amount, credit_card)
diff --git a/test/unit/gateways/banwire_test.rb b/test/unit/gateways/banwire_test.rb
index b28c2411e75..5bbc177913a 100644
--- a/test/unit/gateways/banwire_test.rb
+++ b/test/unit/gateways/banwire_test.rb
@@ -9,10 +9,12 @@ def setup
currency: 'MXN'
)
- @credit_card = credit_card('5204164299999999',
+ @credit_card = credit_card(
+ '5204164299999999',
month: 11,
year: 2012,
- verification_value: '999')
+ verification_value: '999'
+ )
@amount = 100
@options = {
@@ -22,11 +24,13 @@ def setup
description: 'Store purchase'
}
- @amex_credit_card = credit_card('375932134599999',
+ @amex_credit_card = credit_card(
+ '375932134599999',
month: 3,
year: 2017,
first_name: 'Banwire',
- last_name: 'Test Card')
+ last_name: 'Test Card'
+ )
@amex_options = {
order_id: '2',
email: 'test@email.com',
diff --git a/test/unit/gateways/barclaycard_smartpay_test.rb b/test/unit/gateways/barclaycard_smartpay_test.rb
index 9b84e216b0c..ee0c11c4da3 100644
--- a/test/unit/gateways/barclaycard_smartpay_test.rb
+++ b/test/unit/gateways/barclaycard_smartpay_test.rb
@@ -152,9 +152,7 @@ def test_successful_authorize_with_alternate_address
def test_successful_authorize_with_house_number_and_street
response = stub_comms do
- @gateway.authorize(@amount,
- @credit_card,
- @options_with_house_number_and_street)
+ @gateway.authorize(@amount, @credit_card, @options_with_house_number_and_street)
end.check_request do |_endpoint, data, _headers|
assert_match(/billingAddress.street=Top\+Level\+Drive/, data)
assert_match(/billingAddress.houseNumberOrName=1000/, data)
@@ -167,9 +165,7 @@ def test_successful_authorize_with_house_number_and_street
def test_successful_authorize_with_shipping_house_number_and_street
response = stub_comms do
- @gateway.authorize(@amount,
- @credit_card,
- @options_with_shipping_house_number_and_shipping_street)
+ @gateway.authorize(@amount, @credit_card, @options_with_shipping_house_number_and_shipping_street)
end.check_request do |_endpoint, data, _headers|
assert_match(/billingAddress.street=Top\+Level\+Drive/, data)
assert_match(/billingAddress.houseNumberOrName=1000/, data)
diff --git a/test/unit/gateways/blue_pay_test.rb b/test/unit/gateways/blue_pay_test.rb
index 9385bb329ed..d1bc53db5c8 100644
--- a/test/unit/gateways/blue_pay_test.rb
+++ b/test/unit/gateways/blue_pay_test.rb
@@ -220,8 +220,7 @@ def test_cvv_result
def test_message_from
assert_equal 'CVV does not match', @gateway.send(:parse, 'STATUS=2&CVV2=N&AVS=A&MESSAGE=FAILURE').message
- assert_equal 'Street address matches, but postal code does not match.',
- @gateway.send(:parse, 'STATUS=2&CVV2=M&AVS=A&MESSAGE=FAILURE').message
+ assert_equal 'Street address matches, but postal code does not match.', @gateway.send(:parse, 'STATUS=2&CVV2=M&AVS=A&MESSAGE=FAILURE').message
end
def test_passing_stored_credentials_data_for_mit_transaction
@@ -259,12 +258,15 @@ def test_successful_recurring
@gateway.expects(:ssl_post).returns(successful_recurring_response)
response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(@amount, @credit_card,
+ @gateway.recurring(
+ @amount,
+ @credit_card,
billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'),
rebill_start_date: '1 MONTH',
rebill_expression: '14 DAYS',
rebill_cycles: '24',
- rebill_amount: @amount * 4)
+ rebill_amount: @amount * 4
+ )
end
assert_instance_of Response, response
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index 3d763d7abfb..7a05622ddbe 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -945,14 +945,17 @@ def test_successful_purchase_with_travel_data
(params[:industry][:data][:lodging_name] == 'Best Hotel Ever')
end.returns(braintree_result)
- @gateway.purchase(100, credit_card('41111111111111111111'),
+ @gateway.purchase(
+ 100,
+ credit_card('41111111111111111111'),
travel_data: {
travel_package: 'flight',
departure_date: '2050-07-22',
lodging_check_in_date: '2050-07-22',
lodging_check_out_date: '2050-07-25',
lodging_name: 'Best Hotel Ever'
- })
+ }
+ )
end
def test_successful_purchase_with_lodging_data
@@ -964,13 +967,16 @@ def test_successful_purchase_with_lodging_data
(params[:industry][:data][:room_rate] == '80.00')
end.returns(braintree_result)
- @gateway.purchase(100, credit_card('41111111111111111111'),
+ @gateway.purchase(
+ 100,
+ credit_card('41111111111111111111'),
lodging_data: {
folio_number: 'ABC123',
check_in_date: '2050-12-22',
check_out_date: '2050-12-25',
room_rate: '80.00'
- })
+ }
+ )
end
def test_apple_pay_card
@@ -993,11 +999,13 @@ def test_apple_pay_card
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
response = @gateway.authorize(100, credit_card, test: true, order_id: '1')
assert_equal 'transaction_id', response.authorization
@@ -1025,12 +1033,14 @@ def test_android_pay_card
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
payment_cryptogram: '111111111100cryptogram',
source: :android_pay,
- transaction_id: '1234567890')
+ transaction_id: '1234567890'
+ )
response = @gateway.authorize(100, credit_card, test: true, order_id: '1')
assert_equal 'transaction_id', response.authorization
@@ -1058,12 +1068,14 @@ def test_google_pay_card
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
payment_cryptogram: '111111111100cryptogram',
source: :google_pay,
- transaction_id: '1234567890')
+ transaction_id: '1234567890'
+ )
response = @gateway.authorize(100, credit_card, test: true, order_id: '1')
assert_equal 'transaction_id', response.authorization
diff --git a/test/unit/gateways/card_stream_test.rb b/test/unit/gateways/card_stream_test.rb
index 509dc81fc38..f648a960a42 100644
--- a/test/unit/gateways/card_stream_test.rb
+++ b/test/unit/gateways/card_stream_test.rb
@@ -9,11 +9,13 @@ def setup
shared_secret: 'secret'
)
- @visacreditcard = credit_card('4929421234600821',
+ @visacreditcard = credit_card(
+ '4929421234600821',
month: '12',
year: '2014',
verification_value: '356',
- brand: :visa)
+ brand: :visa
+ )
@visacredit_options = {
billing_address: {
@@ -51,15 +53,15 @@ def setup
dynamic_descriptor: 'product'
}
- @amex = credit_card('374245455400001',
+ @amex = credit_card(
+ '374245455400001',
month: '12',
year: 2014,
verification_value: '4887',
- brand: :american_express)
+ brand: :american_express
+ )
- @declined_card = credit_card('4000300011112220',
- month: '9',
- year: '2014')
+ @declined_card = credit_card('4000300011112220', month: '9', year: '2014')
end
def test_successful_visacreditcard_authorization
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index 8b18b2d32de..cf6af56b989 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -8,29 +8,35 @@ def setup
@amount = 1204
@credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123')
- @google_pay = network_tokenization_credit_card('4005550000000019',
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :google_pay,
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- transaction_id: '13456789')
- @apple_pay = network_tokenization_credit_card('4005550000000019',
+ transaction_id: '13456789'
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- transaction_id: '13456789')
- @no_supported_source = network_tokenization_credit_card('4005550000000019',
+ transaction_id: '13456789'
+ )
+ @no_supported_source = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :no_source,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
@options = {}
@post = {}
diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb
index 1133152a2c2..625953f57f0 100644
--- a/test/unit/gateways/credorax_test.rb
+++ b/test/unit/gateways/credorax_test.rb
@@ -43,13 +43,16 @@ def setup
}
}
- @nt_credit_card = network_tokenization_credit_card('4176661000001015',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4176661000001015',
brand: 'visa',
eci: '07',
source: :network_token,
- payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA=')
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA='
+ )
- @apple_pay_card = network_tokenization_credit_card('4176661000001015',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
month: 10,
year: Time.new.year + 2,
first_name: 'John',
@@ -58,7 +61,8 @@ def setup
payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
end
def test_supported_card_types
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index af92b99888b..6c9bc3091c5 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -10,10 +10,12 @@ def setup
private_key: "NYlM1sgultLjvgaraWvDCXykdz1buqOW8yXE3pMlmxQ=\n"
)
@bank_account = check(account_number: '4100', routing_number: '121042882')
- @credit_card = credit_card('4111111111111111',
+ @credit_card = credit_card(
+ '4111111111111111',
verification_value: '987',
month: 12,
- year: 2031)
+ year: 2031
+ )
@apple_pay = network_tokenization_credit_card(
'4111111111111111',
payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 29f08a73d6f..b87a5b8a493 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -384,11 +384,13 @@ def test_successful_network_token_purchase_single_request_ignore_avs
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
options = @options.merge(ignore_avs: true)
assert response = @gateway.purchase(@amount, credit_card, options)
assert_success response
@@ -439,11 +441,13 @@ def test_successful_network_token_purchase_single_request_ignore_cvv
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
options = @options.merge(ignore_cvv: true)
assert response = @gateway.purchase(@amount, credit_card, options)
assert_success response
@@ -880,11 +884,13 @@ def test_unsuccessful_verify
end
def test_successful_auth_with_network_tokenization_for_visa
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
response = stub_comms do
@gateway.authorize(@amount, credit_card, @options)
@@ -897,11 +903,13 @@ def test_successful_auth_with_network_tokenization_for_visa
end
def test_successful_purchase_with_network_tokenization_for_visa
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
response = stub_comms do
@gateway.purchase(@amount, credit_card, @options)
@@ -920,11 +928,13 @@ def test_successful_auth_with_network_tokenization_for_mastercard
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('5555555555554444',
+ credit_card = network_tokenization_credit_card(
+ '5555555555554444',
brand: 'master',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram'
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
@@ -937,11 +947,13 @@ def test_successful_auth_with_network_tokenization_for_amex
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
transaction_id: '123',
eci: '05',
- payment_cryptogram: Base64.encode64('111111111100cryptogram'))
+ payment_cryptogram: Base64.encode64('111111111100cryptogram')
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
@@ -1508,9 +1520,7 @@ def test_cvv_mismatch_auto_void_failed
def test_able_to_properly_handle_40bytes_cryptogram
long_cryptogram = "NZwc40C4eTDWHVDXPekFaKkNYGk26w+GYDZmU50cATbjqOpNxR/eYA==\n"
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'american_express',
- payment_cryptogram: long_cryptogram)
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'american_express', payment_cryptogram: long_cryptogram)
stub_comms do
@gateway.authorize(@amount, credit_card, @options)
@@ -1524,9 +1534,7 @@ def test_able_to_properly_handle_40bytes_cryptogram
end
def test_able_to_properly_handle_20bytes_cryptogram
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'american_express',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'american_express', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
stub_comms do
@gateway.authorize(@amount, credit_card, @options)
@@ -1539,9 +1547,7 @@ def test_able_to_properly_handle_20bytes_cryptogram
def test_raises_error_on_network_token_with_an_underlying_discover_card
error = assert_raises ArgumentError do
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'discover',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'discover', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
@gateway.authorize(100, credit_card, @options)
end
@@ -1550,9 +1556,7 @@ def test_raises_error_on_network_token_with_an_underlying_discover_card
def test_raises_error_on_network_token_with_an_underlying_apms
error = assert_raises ArgumentError do
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'sodexo',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'sodexo', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
@gateway.authorize(100, credit_card, @options)
end
diff --git a/test/unit/gateways/d_local_test.rb b/test/unit/gateways/d_local_test.rb
index d5eb9c9322c..7bed8cc718a 100644
--- a/test/unit/gateways/d_local_test.rb
+++ b/test/unit/gateways/d_local_test.rb
@@ -65,8 +65,7 @@ def test_purchase_with_installments
end
def test_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card)
end.check_request do |_endpoint, data, _headers|
@@ -77,8 +76,7 @@ def test_purchase_with_network_tokens
def test_purchase_with_network_tokens_and_store_credential_type_subscription
options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -90,8 +88,7 @@ def test_purchase_with_network_tokens_and_store_credential_type_subscription
def test_purchase_with_network_tokens_and_store_credential_type_uneschedule
options = @options.merge!(stored_credential: stored_credential(:merchant, :unscheduled, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -103,8 +100,7 @@ def test_purchase_with_network_tokens_and_store_credential_type_uneschedule
def test_purchase_with_network_tokens_and_store_credential_usage_first
options = @options.merge!(stored_credential: stored_credential(:cardholder, :initial))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -116,8 +112,7 @@ def test_purchase_with_network_tokens_and_store_credential_usage_first
def test_purchase_with_network_tokens_and_store_credential_type_card_on_file_and_credential_usage_used
options = @options.merge!(stored_credential: stored_credential(:cardholder, :unscheduled, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -130,8 +125,7 @@ def test_purchase_with_network_tokens_and_store_credential_type_card_on_file_and
def test_purchase_with_network_tokens_and_store_credential_usage
options = @options.merge!(stored_credential: stored_credential(:cardholder, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb
index 277693e4b4a..500707694a1 100644
--- a/test/unit/gateways/ebanx_test.rb
+++ b/test/unit/gateways/ebanx_test.rb
@@ -28,7 +28,7 @@ def test_successful_purchase
def test_successful_purchase_with_optional_processing_type_header
response = stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@accepted_amount, @credit_card, @options.merge(processing_type: 'local'))
+ @gateway.purchase(@amount, @credit_card, @options.merge(processing_type: 'local'))
end.check_request do |_method, _endpoint, _data, headers|
assert_equal 'local', headers['x-ebanx-api-processing-type']
end.respond_with(successful_purchase_response)
diff --git a/test/unit/gateways/epay_test.rb b/test/unit/gateways/epay_test.rb
index cfebf4b8447..91a03ed50e5 100644
--- a/test/unit/gateways/epay_test.rb
+++ b/test/unit/gateways/epay_test.rb
@@ -26,8 +26,7 @@ def test_failed_purchase
assert response = @gateway.authorize(100, @credit_card)
assert_failure response
- assert_equal 'The payment was declined. Try again in a moment or try with another credit card.',
- response.message
+ assert_equal 'The payment was declined. Try again in a moment or try with another credit card.', response.message
end
def test_successful_3ds_purchase
@@ -51,8 +50,7 @@ def test_invalid_characters_in_response
assert response = @gateway.authorize(100, @credit_card)
assert_failure response
- assert_equal 'The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.
Denied - Call your bank for information',
- response.message
+ assert_equal 'The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.
Denied - Call your bank for information', response.message
end
def test_failed_response_on_purchase
diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb
index 4ed739ca9a4..ec70917a51a 100644
--- a/test/unit/gateways/eway_rapid_test.rb
+++ b/test/unit/gateways/eway_rapid_test.rb
@@ -194,7 +194,9 @@ def test_failed_purchase_with_multiple_messages
def test_purchase_with_all_options
response = stub_comms do
- @gateway.purchase(200, @credit_card,
+ @gateway.purchase(
+ 200,
+ @credit_card,
transaction_type: 'CustomTransactionType',
redirect_url: 'http://awesomesauce.com',
ip: '0.0.0.0',
@@ -230,7 +232,8 @@ def test_purchase_with_all_options
country: 'US',
phone: '1115555555',
fax: '1115556666'
- })
+ }
+ )
end.check_request do |_endpoint, data, _headers|
assert_match(%r{"TransactionType":"CustomTransactionType"}, data)
assert_match(%r{"RedirectUrl":"http://awesomesauce.com"}, data)
diff --git a/test/unit/gateways/exact_test.rb b/test/unit/gateways/exact_test.rb
index 5a1257add89..2986777bfa6 100644
--- a/test/unit/gateways/exact_test.rb
+++ b/test/unit/gateways/exact_test.rb
@@ -49,9 +49,10 @@ def test_failed_purchase
end
def test_expdate
- assert_equal('%02d%s' % [@credit_card.month,
- @credit_card.year.to_s[-2..-1]],
- @gateway.send(:expdate, @credit_card))
+ assert_equal(
+ '%02d%s' % [@credit_card.month, @credit_card.year.to_s[-2..-1]],
+ @gateway.send(:expdate, @credit_card)
+ )
end
def test_soap_fault
diff --git a/test/unit/gateways/firstdata_e4_test.rb b/test/unit/gateways/firstdata_e4_test.rb
index 21a0a0da180..c18b5941cc9 100755
--- a/test/unit/gateways/firstdata_e4_test.rb
+++ b/test/unit/gateways/firstdata_e4_test.rb
@@ -63,8 +63,7 @@ def test_successful_purchase_with_token
def test_successful_purchase_with_specified_currency_and_token
options_with_specified_currency = @options.merge({ currency: 'GBP' })
@gateway.expects(:ssl_post).returns(successful_purchase_with_specified_currency_response)
- assert response = @gateway.purchase(@amount, '8938737759041111;visa;Longbob;Longsen;9;2014',
- options_with_specified_currency)
+ assert response = @gateway.purchase(@amount, '8938737759041111;visa;Longbob;Longsen;9;2014', options_with_specified_currency)
assert_success response
assert_equal 'GBP', response.params['currency']
end
@@ -1049,7 +1048,7 @@ def no_transaction_response
read: true
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1086,7 +1085,7 @@ def bad_credentials_response
http_version: '1.1'
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def successful_void_response
diff --git a/test/unit/gateways/firstdata_e4_v27_test.rb b/test/unit/gateways/firstdata_e4_v27_test.rb
index 02f982bb551..a4301598f65 100644
--- a/test/unit/gateways/firstdata_e4_v27_test.rb
+++ b/test/unit/gateways/firstdata_e4_v27_test.rb
@@ -1001,7 +1001,7 @@ def no_transaction_response
read: true
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1038,7 +1038,7 @@ def bad_credentials_response
http_version: '1.1'
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def successful_void_response
diff --git a/test/unit/gateways/garanti_test.rb b/test/unit/gateways/garanti_test.rb
index cc3416e0f89..ad8ed645b9c 100644
--- a/test/unit/gateways/garanti_test.rb
+++ b/test/unit/gateways/garanti_test.rb
@@ -9,7 +9,7 @@ def setup
Base.mode = :test
@gateway = GarantiGateway.new(login: 'a', password: 'b', terminal_id: 'c', merchant_id: 'd')
- @credit_card = credit_card(4242424242424242)
+ @credit_card = credit_card('4242424242424242')
@amount = 1000 # 1000 cents, 10$
@options = {
diff --git a/test/unit/gateways/gateway_test.rb b/test/unit/gateways/gateway_test.rb
index 68d6d0a441e..27ccbd14215 100644
--- a/test/unit/gateways/gateway_test.rb
+++ b/test/unit/gateways/gateway_test.rb
@@ -28,8 +28,7 @@ def test_should_validate_supported_countries
assert_nothing_raised do
Gateway.supported_countries = all_country_codes
- assert Gateway.supported_countries == all_country_codes,
- 'List of supported countries not properly set'
+ assert Gateway.supported_countries == all_country_codes, 'List of supported countries not properly set'
end
end
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index b6ce2a95df3..efb0e69089e 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -9,26 +9,32 @@ def setup
secret_api_key: '109H/288H*50Y18W4/0G8571F245KA=')
@credit_card = credit_card('4567350000427977')
- @apple_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
month: 10,
year: 24,
first_name: 'John',
last_name: 'Smith',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
- @google_pay_pan_only = credit_card('4444333322221111',
+ @google_pay_pan_only = credit_card(
+ '4444333322221111',
month: '01',
- year: Time.new.year + 2)
+ year: Time.new.year + 2
+ )
@declined_card = credit_card('5424180279791732')
@accepted_amount = 4005
diff --git a/test/unit/gateways/hps_test.rb b/test/unit/gateways/hps_test.rb
index 6707d9d703e..1f8832e7ab7 100644
--- a/test/unit/gateways/hps_test.rb
+++ b/test/unit/gateways/hps_test.rb
@@ -288,11 +288,13 @@ def test_account_number_scrubbing
def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -301,11 +303,13 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
def test_failed_purchase_with_apple_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -314,10 +318,12 @@ def test_failed_purchase_with_apple_pay_raw_cryptogram_with_eci
def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -326,10 +332,12 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
def test_failed_purchase_with_apple_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -338,11 +346,13 @@ def test_failed_purchase_with_apple_pay_raw_cryptogram_without_eci
def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -351,11 +361,13 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
def test_failed_auth_with_apple_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -364,10 +376,12 @@ def test_failed_auth_with_apple_pay_raw_cryptogram_with_eci
def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -376,10 +390,12 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
def test_failed_auth_with_apple_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -388,11 +404,13 @@ def test_failed_auth_with_apple_pay_raw_cryptogram_without_eci
def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -401,11 +419,13 @@ def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
def test_failed_purchase_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -414,10 +434,12 @@ def test_failed_purchase_with_android_pay_raw_cryptogram_with_eci
def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -426,10 +448,12 @@ def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
def test_failed_purchase_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -438,11 +462,13 @@ def test_failed_purchase_with_android_pay_raw_cryptogram_without_eci
def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -451,11 +477,13 @@ def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
def test_failed_auth_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -464,10 +492,12 @@ def test_failed_auth_with_android_pay_raw_cryptogram_with_eci
def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -476,10 +506,12 @@ def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
def test_failed_auth_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -488,11 +520,13 @@ def test_failed_auth_with_android_pay_raw_cryptogram_without_eci
def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -501,11 +535,13 @@ def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
def test_failed_purchase_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -514,10 +550,12 @@ def test_failed_purchase_with_google_pay_raw_cryptogram_with_eci
def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -526,10 +564,12 @@ def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
def test_failed_purchase_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -538,11 +578,13 @@ def test_failed_purchase_with_google_pay_raw_cryptogram_without_eci
def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -551,11 +593,13 @@ def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
def test_failed_auth_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -564,10 +608,12 @@ def test_failed_auth_with_google_pay_raw_cryptogram_with_eci
def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -576,10 +622,12 @@ def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
def test_failed_auth_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
diff --git a/test/unit/gateways/mercado_pago_test.rb b/test/unit/gateways/mercado_pago_test.rb
index 6af0e181c6d..a25401c202d 100644
--- a/test/unit/gateways/mercado_pago_test.rb
+++ b/test/unit/gateways/mercado_pago_test.rb
@@ -6,24 +6,30 @@ class MercadoPagoTest < Test::Unit::TestCase
def setup
@gateway = MercadoPagoGateway.new(access_token: 'access_token')
@credit_card = credit_card
- @elo_credit_card = credit_card('5067268650517446',
+ @elo_credit_card = credit_card(
+ '5067268650517446',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @cabal_credit_card = credit_card('6035227716427021',
+ verification_value: '737'
+ )
+ @cabal_credit_card = credit_card(
+ '6035227716427021',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @naranja_credit_card = credit_card('5895627823453005',
+ verification_value: '737'
+ )
+ @naranja_credit_card = credit_card(
+ '5895627823453005',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
- verification_value: '123')
+ verification_value: '123'
+ )
@amount = 100
@options = {
diff --git a/test/unit/gateways/moneris_test.rb b/test/unit/gateways/moneris_test.rb
index 5c7ee922fbc..2edc338cd31 100644
--- a/test/unit/gateways/moneris_test.rb
+++ b/test/unit/gateways/moneris_test.rb
@@ -38,7 +38,9 @@ def test_successful_purchase
def test_successful_mpi_cavv_purchase
@gateway.expects(:ssl_post).returns(successful_cavv_purchase_response)
- assert response = @gateway.purchase(100, @credit_card,
+ assert response = @gateway.purchase(
+ 100,
+ @credit_card,
@options.merge(
three_d_secure: {
version: '2',
@@ -47,7 +49,8 @@ def test_successful_mpi_cavv_purchase
three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
@@ -55,7 +58,9 @@ def test_successful_mpi_cavv_purchase
def test_failed_mpi_cavv_purchase
@gateway.expects(:ssl_post).returns(failed_cavv_purchase_response)
- assert response = @gateway.purchase(100, @credit_card,
+ assert response = @gateway.purchase(
+ 100,
+ @credit_card,
@options.merge(
three_d_secure: {
version: '2',
@@ -64,7 +69,8 @@ def test_failed_mpi_cavv_purchase
three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_failure response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
@@ -128,9 +134,11 @@ def test_successful_subsequent_purchase_with_credential_on_file
def test_successful_purchase_with_network_tokenization
@gateway.expects(:ssl_post).returns(successful_purchase_network_tokenization)
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(100, @credit_card, @options)
assert_success response
assert_equal '101965-0_10;0bbb277b543a17b6781243889a689573', response.authorization
@@ -277,9 +285,11 @@ def test_successful_purchase_with_vault
def test_successful_authorize_with_network_tokenization
@gateway.expects(:ssl_post).returns(successful_authorization_network_tokenization)
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.authorize(100, @credit_card, @options)
assert_success response
assert_equal '109232-0_10;d88d9f5f3472898832c54d6b5572757e', response.authorization
diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb
index 160ea8a3f86..3736b25b6bb 100644
--- a/test/unit/gateways/nmi_test.rb
+++ b/test/unit/gateways/nmi_test.rb
@@ -588,8 +588,7 @@ def test_blank_cvv_not_sent
end
def test_supported_countries
- assert_equal 1,
- (['US'] | NmiGateway.supported_countries).size
+ assert_equal 1, (['US'] | NmiGateway.supported_countries).size
end
def test_supported_card_types
@@ -824,8 +823,7 @@ def test_verify(options = {})
assert_match(/payment=creditcard/, data)
assert_match(/ccnumber=#{@credit_card.number}/, data)
assert_match(/cvv=#{@credit_card.verification_value}/, data)
- assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/,
- data)
+ assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data)
test_level3_options(data) if options.any?
end.respond_with(successful_validate_response)
diff --git a/test/unit/gateways/opp_test.rb b/test/unit/gateways/opp_test.rb
index 6dfcf179097..ec7af16f8bf 100644
--- a/test/unit/gateways/opp_test.rb
+++ b/test/unit/gateways/opp_test.rb
@@ -202,7 +202,8 @@ def post_scrubbed
end
def successful_response(type, id)
- OppMockResponse.new(200,
+ OppMockResponse.new(
+ 200,
JSON.generate({
'id' => id,
'paymentType' => type,
@@ -224,11 +225,13 @@ def successful_response(type, id)
'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage',
'timestamp' => '2015-06-20 19:31:01+0000',
'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9'
- }))
+ })
+ )
end
def successful_store_response(id)
- OppMockResponse.new(200,
+ OppMockResponse.new(
+ 200,
JSON.generate({
'id' => id,
'result' => {
@@ -245,11 +248,13 @@ def successful_store_response(id)
'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage',
'timestamp' => '2015-06-20 19:31:01+0000',
'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9'
- }))
+ })
+ )
end
def failed_response(type, id, code = '100.100.101')
- OppMockResponse.new(400,
+ OppMockResponse.new(
+ 400,
JSON.generate({
'id' => id,
'paymentType' => type,
@@ -268,11 +273,13 @@ def failed_response(type, id, code = '100.100.101')
'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage',
'timestamp' => '2015-06-20 20:40:26+0000',
'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d'
- }))
+ })
+ )
end
def failed_store_response(id, code = '100.100.101')
- OppMockResponse.new(400,
+ OppMockResponse.new(
+ 400,
JSON.generate({
'id' => id,
'result' => {
@@ -289,7 +296,8 @@ def failed_store_response(id, code = '100.100.101')
'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage',
'timestamp' => '2015-06-20 20:40:26+0000',
'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d'
- }))
+ })
+ )
end
class OppMockResponse
diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb
index 0e4b306bc5b..fad86ef4c85 100644
--- a/test/unit/gateways/orbital_test.rb
+++ b/test/unit/gateways/orbital_test.rb
@@ -558,9 +558,7 @@ def test_truncates_address
end
def test_truncates_name
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: address)
@@ -649,8 +647,7 @@ def test_address_format
response = stub_comms do
assert_deprecation_warning do
- @gateway.add_customer_profile(credit_card,
- billing_address: address_with_invalid_chars)
+ @gateway.add_customer_profile(credit_card, billing_address: address_with_invalid_chars)
end
end.check_request do |_endpoint, data, _headers|
assert_match(/456 Main Street, data)
@@ -661,9 +658,7 @@ def test_address_format
end
def test_truncates_by_byte_length
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
long_address = address(
address1: '456 Stréêt Name is Really Long',
@@ -699,8 +694,7 @@ def test_truncates_by_byte_length
response = stub_comms do
assert_deprecation_warning do
- @gateway.add_customer_profile(credit_card,
- billing_address: long_address)
+ @gateway.add_customer_profile(credit_card, billing_address: long_address)
end
end.check_request do |_endpoint, data, _headers|
assert_match(/456 Stréêt Name is Really Lo, data)
@@ -777,9 +771,7 @@ def test_name_sends_for_credit_card_with_address
dest_country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, address: address)
@@ -817,9 +809,7 @@ def test_name_sends_for_echeck_with_no_address
end
def test_does_not_send_for_credit_card_with_no_address
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, address: nil, billing_address: nil)
@@ -840,9 +830,7 @@ def test_avs_name_falls_back_to_billing_address
country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: nil,
- last_name: '')
+ card = credit_card('4242424242424242', first_name: nil, last_name: '')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: billing_address)
@@ -863,9 +851,7 @@ def test_completely_blank_name
country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: nil,
- last_name: nil)
+ card = credit_card('4242424242424242', first_name: nil, last_name: nil)
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: billing_address)
@@ -1091,13 +1077,15 @@ def test_managed_billing
response = stub_comms do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
assert_deprecation_warning do
- @gateway.add_customer_profile(credit_card,
+ @gateway.add_customer_profile(
+ credit_card,
managed_billing: {
start_date: '10-10-2014',
end_date: '10-10-2015',
max_dollar_value: 1500,
max_transactions: 12
- })
+ }
+ )
end
end
end.check_request do |_endpoint, data, _headers|
@@ -1545,8 +1533,7 @@ def test_cc_account_num_is_removed_from_response
response = nil
assert_deprecation_warning do
- response = @gateway.add_customer_profile(credit_card,
- billing_address: address)
+ response = @gateway.add_customer_profile(credit_card, billing_address: address)
end
assert_instance_of Response, response
diff --git a/test/unit/gateways/pac_net_raven_test.rb b/test/unit/gateways/pac_net_raven_test.rb
index e98214fe6bd..d206b211448 100644
--- a/test/unit/gateways/pac_net_raven_test.rb
+++ b/test/unit/gateways/pac_net_raven_test.rb
@@ -337,7 +337,8 @@ def test_post_data
@gateway.stubs(request_id: 'wouykiikdvqbwwxueppby')
@gateway.stubs(timestamp: '2013-10-08T14:31:54.Z')
- assert_equal "PymtType=cc_preauth&RAPIVersion=2&UserName=user&Timestamp=2013-10-08T14%3A31%3A54.Z&RequestID=wouykiikdvqbwwxueppby&Signature=7794efc8c0d39f0983edc10f778e6143ba13531d&CardNumber=4242424242424242&Expiry=09#{@credit_card.year.to_s[-2..-1]}&CVV2=123&Currency=USD&BillingStreetAddressLineOne=Address+1&BillingStreetAddressLineFour=Address+2&BillingPostalCode=ZIP123",
+ assert_equal(
+ "PymtType=cc_preauth&RAPIVersion=2&UserName=user&Timestamp=2013-10-08T14%3A31%3A54.Z&RequestID=wouykiikdvqbwwxueppby&Signature=7794efc8c0d39f0983edc10f778e6143ba13531d&CardNumber=4242424242424242&Expiry=09#{@credit_card.year.to_s[-2..-1]}&CVV2=123&Currency=USD&BillingStreetAddressLineOne=Address+1&BillingStreetAddressLineFour=Address+2&BillingPostalCode=ZIP123",
@gateway.send(:post_data, 'cc_preauth', {
'CardNumber' => @credit_card.number,
'Expiry' => @gateway.send(:expdate, @credit_card),
@@ -347,6 +348,7 @@ def test_post_data
'BillingStreetAddressLineFour' => 'Address 2',
'BillingPostalCode' => 'ZIP123'
})
+ )
end
def test_signature_for_cc_preauth_action
diff --git a/test/unit/gateways/paybox_direct_test.rb b/test/unit/gateways/paybox_direct_test.rb
index a10fab948bc..0676dc82ef9 100644
--- a/test/unit/gateways/paybox_direct_test.rb
+++ b/test/unit/gateways/paybox_direct_test.rb
@@ -9,8 +9,7 @@ def setup
password: 'p'
)
- @credit_card = credit_card('1111222233334444',
- brand: 'visa')
+ @credit_card = credit_card('1111222233334444', brand: 'visa')
@amount = 100
@options = {
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index dfbda5f591a..bb92e56e002 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -843,7 +843,7 @@ def failed_purchase_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def failed_purchase_response_for_insufficient_funds
@@ -928,7 +928,7 @@ def failed_refund_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def successful_void_response
@@ -973,7 +973,7 @@ def failed_void_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def failed_capture_response
@@ -1013,7 +1013,7 @@ def failed_capture_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def invalid_token_response
@@ -1052,7 +1052,7 @@ def invalid_token_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def invalid_token_response_integration
@@ -1077,7 +1077,7 @@ def invalid_token_response_integration
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1102,6 +1102,6 @@ def bad_credentials_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError'])
end
end
diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb
index 3d0a4cdb5fb..69fc901992f 100644
--- a/test/unit/gateways/payflow_test.rb
+++ b/test/unit/gateways/payflow_test.rb
@@ -465,9 +465,12 @@ def test_store_returns_error
def test_initial_recurring_transaction_missing_parameters
assert_raises ArgumentError do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(@amount, @credit_card,
+ @gateway.recurring(
+ @amount,
+ @credit_card,
periodicity: :monthly,
- initial_transaction: {})
+ initial_transaction: {}
+ )
end
end
end
@@ -475,9 +478,12 @@ def test_initial_recurring_transaction_missing_parameters
def test_initial_purchase_missing_amount
assert_raises ArgumentError do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(@amount, @credit_card,
+ @gateway.recurring(
+ @amount,
+ @credit_card,
periodicity: :monthly,
- initial_transaction: { amount: :purchase })
+ initial_transaction: { amount: :purchase }
+ )
end
end
end
diff --git a/test/unit/gateways/paymentez_test.rb b/test/unit/gateways/paymentez_test.rb
index a3019c5f9c0..07223ab61d9 100644
--- a/test/unit/gateways/paymentez_test.rb
+++ b/test/unit/gateways/paymentez_test.rb
@@ -6,13 +6,15 @@ class PaymentezTest < Test::Unit::TestCase
def setup
@gateway = PaymentezGateway.new(application_code: 'foo', app_key: 'bar')
@credit_card = credit_card
- @elo_credit_card = credit_card('6362970000457013',
+ @elo_credit_card = credit_card(
+ '6362970000457013',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
@amount = 100
@options = {
diff --git a/test/unit/gateways/paypal/paypal_common_api_test.rb b/test/unit/gateways/paypal/paypal_common_api_test.rb
index da1592285f4..ebf2a530be4 100644
--- a/test/unit/gateways/paypal/paypal_common_api_test.rb
+++ b/test/unit/gateways/paypal/paypal_common_api_test.rb
@@ -190,10 +190,14 @@ def test_build_reference_transaction_request
end
def test_build_reference_transaction_gets_ip
- request = REXML::Document.new(@gateway.send(:build_reference_transaction_request,
- 100,
- reference_id: 'id',
- ip: '127.0.0.1'))
+ request = REXML::Document.new(
+ @gateway.send(
+ :build_reference_transaction_request,
+ 100,
+ reference_id: 'id',
+ ip: '127.0.0.1'
+ )
+ )
assert_equal '100', REXML::XPath.first(request, '//n2:PaymentDetails/n2:OrderTotal').text
assert_equal 'id', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text
assert_equal '127.0.0.1', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text
diff --git a/test/unit/gateways/paypal_digital_goods_test.rb b/test/unit/gateways/paypal_digital_goods_test.rb
index 605fcadb518..886253a6673 100644
--- a/test/unit/gateways/paypal_digital_goods_test.rb
+++ b/test/unit/gateways/paypal_digital_goods_test.rb
@@ -34,60 +34,78 @@ def test_test_redirect_url
def test_setup_request_invalid_requests
assert_raise ArgumentError do
- @gateway.setup_purchase(100,
+ @gateway.setup_purchase(
+ 100,
ip: '127.0.0.1',
description: 'Test Title',
return_url: 'http://return.url',
- cancel_return_url: 'http://cancel.url')
+ cancel_return_url: 'http://cancel.url'
+ )
end
assert_raise ArgumentError do
- @gateway.setup_purchase(100,
+ @gateway.setup_purchase(
+ 100,
ip: '127.0.0.1',
description: 'Test Title',
return_url: 'http://return.url',
cancel_return_url: 'http://cancel.url',
- items: [])
+ items: []
+ )
end
assert_raise ArgumentError do
- @gateway.setup_purchase(100,
+ @gateway.setup_purchase(
+ 100,
ip: '127.0.0.1',
description: 'Test Title',
return_url: 'http://return.url',
cancel_return_url: 'http://cancel.url',
- items: [Hash.new])
+ items: [Hash.new]
+ )
end
assert_raise ArgumentError do
- @gateway.setup_purchase(100,
+ @gateway.setup_purchase(
+ 100,
ip: '127.0.0.1',
description: 'Test Title',
return_url: 'http://return.url',
cancel_return_url: 'http://cancel.url',
- items: [{ name: 'Charge',
- number: '1',
- quantity: '1',
- amount: 100,
- description: 'Description',
- category: 'Physical' }])
+ items: [
+ {
+ name: 'Charge',
+ number: '1',
+ quantity: '1',
+ amount: 100,
+ description: 'Description',
+ category: 'Physical'
+ }
+ ]
+ )
end
end
def test_build_setup_request_valid
@gateway.expects(:ssl_post).returns(successful_setup_response)
- @gateway.setup_purchase(100,
+ @gateway.setup_purchase(
+ 100,
ip: '127.0.0.1',
description: 'Test Title',
return_url: 'http://return.url',
cancel_return_url: 'http://cancel.url',
- items: [{ name: 'Charge',
- number: '1',
- quantity: '1',
- amount: 100,
- description: 'Description',
- category: 'Digital' }])
+ items: [
+ {
+ name: 'Charge',
+ number: '1',
+ quantity: '1',
+ amount: 100,
+ description: 'Description',
+ category: 'Digital'
+ }
+ ]
+ )
end
private
diff --git a/test/unit/gateways/paypal_express_test.rb b/test/unit/gateways/paypal_express_test.rb
index afd7fb5cbf0..5be4717bfed 100644
--- a/test/unit/gateways/paypal_express_test.rb
+++ b/test/unit/gateways/paypal_express_test.rb
@@ -243,22 +243,28 @@ def test_does_not_include_flatrate_shipping_options_if_not_specified
end
def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_request
- xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0,
- {
- currency: 'AUD',
- shipping_options: [
- {
- default: true,
- name: 'first one',
- amount: 1000
- },
- {
- default: false,
- name: 'second one',
- amount: 2000
- }
- ]
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_setup_request,
+ 'SetExpressCheckout',
+ 0,
+ {
+ currency: 'AUD',
+ shipping_options: [
+ {
+ default: true,
+ name: 'first one',
+ amount: 1000
+ },
+ {
+ default: false,
+ name: 'second one',
+ amount: 2000
+ }
+ ]
+ }
+ )
+ )
assert_equal 'true', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionIsDefault').text
assert_equal 'first one', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionName').text
@@ -272,18 +278,24 @@ def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_requ
end
def test_address_is_included_if_specified
- xml = REXML::Document.new(@gateway.send(:build_setup_request, 'Sale', 0,
- {
- currency: 'GBP',
- address: {
- name: 'John Doe',
- address1: '123 somewhere',
- city: 'Townville',
- country: 'Canada',
- zip: 'k1l4p2',
- phone: '1231231231'
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_setup_request,
+ 'Sale',
+ 0,
+ {
+ currency: 'GBP',
+ address: {
+ name: 'John Doe',
+ address1: '123 somewhere',
+ city: 'Townville',
+ country: 'Canada',
+ zip: 'k1l4p2',
+ phone: '1231231231'
+ }
}
- }))
+ )
+ )
assert_equal 'John Doe', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Name').text
assert_equal '123 somewhere', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Street1').text
@@ -312,30 +324,36 @@ def test_removes_fractional_amounts_with_twd_currency
end
def test_fractional_discounts_are_correctly_calculated_with_jpy_currency
- xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250,
- {
- items: [
- {
- name: 'item one',
- description: 'description',
- amount: 15000,
- number: 1,
- quantity: 1
- },
- {
- name: 'Discount',
- description: 'Discount',
- amount: -750,
- number: 2,
- quantity: 1
- }
- ],
- subtotal: 14250,
- currency: 'JPY',
- shipping: 0,
- handling: 0,
- tax: 0
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_setup_request,
+ 'SetExpressCheckout',
+ 14250,
+ {
+ items: [
+ {
+ name: 'item one',
+ description: 'description',
+ amount: 15000,
+ number: 1,
+ quantity: 1
+ },
+ {
+ name: 'Discount',
+ description: 'Discount',
+ amount: -750,
+ number: 2,
+ quantity: 1
+ }
+ ],
+ subtotal: 14250,
+ currency: 'JPY',
+ shipping: 0,
+ handling: 0,
+ tax: 0
+ }
+ )
+ )
assert_equal '142', REXML::XPath.first(xml, '//n2:OrderTotal').text
assert_equal '142', REXML::XPath.first(xml, '//n2:ItemTotal').text
@@ -345,30 +363,36 @@ def test_fractional_discounts_are_correctly_calculated_with_jpy_currency
end
def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency
- xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14300,
- {
- items: [
- {
- name: 'item one',
- description: 'description',
- amount: 15000,
- number: 1,
- quantity: 1
- },
- {
- name: 'Discount',
- description: 'Discount',
- amount: -700,
- number: 2,
- quantity: 1
- }
- ],
- subtotal: 14300,
- currency: 'JPY',
- shipping: 0,
- handling: 0,
- tax: 0
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_setup_request,
+ 'SetExpressCheckout',
+ 14300,
+ {
+ items: [
+ {
+ name: 'item one',
+ description: 'description',
+ amount: 15000,
+ number: 1,
+ quantity: 1
+ },
+ {
+ name: 'Discount',
+ description: 'Discount',
+ amount: -700,
+ number: 2,
+ quantity: 1
+ }
+ ],
+ subtotal: 14300,
+ currency: 'JPY',
+ shipping: 0,
+ handling: 0,
+ tax: 0
+ }
+ )
+ )
assert_equal '143', REXML::XPath.first(xml, '//n2:OrderTotal').text
assert_equal '143', REXML::XPath.first(xml, '//n2:ItemTotal').text
@@ -378,30 +402,36 @@ def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency
end
def test_fractional_discounts_are_correctly_calculated_with_usd_currency
- xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250,
- {
- items: [
- {
- name: 'item one',
- description: 'description',
- amount: 15000,
- number: 1,
- quantity: 1
- },
- {
- name: 'Discount',
- description: 'Discount',
- amount: -750,
- number: 2,
- quantity: 1
- }
- ],
- subtotal: 14250,
- currency: 'USD',
- shipping: 0,
- handling: 0,
- tax: 0
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_setup_request,
+ 'SetExpressCheckout',
+ 14250,
+ {
+ items: [
+ {
+ name: 'item one',
+ description: 'description',
+ amount: 15000,
+ number: 1,
+ quantity: 1
+ },
+ {
+ name: 'Discount',
+ description: 'Discount',
+ amount: -750,
+ number: 2,
+ quantity: 1
+ }
+ ],
+ subtotal: 14250,
+ currency: 'USD',
+ shipping: 0,
+ handling: 0,
+ tax: 0
+ }
+ )
+ )
assert_equal '142.50', REXML::XPath.first(xml, '//n2:OrderTotal').text
assert_equal '142.50', REXML::XPath.first(xml, '//n2:ItemTotal').text
@@ -454,25 +484,31 @@ def test_button_source
end
def test_items_are_included_if_specified_in_build_sale_or_authorization_request
- xml = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Sale', 100,
- {
- items: [
- {
- name: 'item one',
- description: 'item one description',
- amount: 10000,
- number: 1,
- quantity: 3
- },
- {
- name: 'item two',
- description: 'item two description',
- amount: 20000,
- number: 2,
- quantity: 4
- }
- ]
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_sale_or_authorization_request,
+ 'Sale',
+ 100,
+ {
+ items: [
+ {
+ name: 'item one',
+ description: 'item one description',
+ amount: 10000,
+ number: 1,
+ quantity: 3
+ },
+ {
+ name: 'item two',
+ description: 'item two description',
+ amount: 20000,
+ number: 2,
+ quantity: 4
+ }
+ ]
+ }
+ )
+ )
assert_equal 'item one', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Name').text
assert_equal 'item one description', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Description').text
@@ -548,15 +584,21 @@ def test_agreement_details_failure
def test_build_reference_transaction_test
PaypalExpressGateway.application_id = 'ActiveMerchant_FOO'
- xml = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Sale', 2000,
- {
- reference_id: 'ref_id',
- payment_type: 'Any',
- invoice_id: 'invoice_id',
- description: 'Description',
- ip: '127.0.0.1',
- merchant_session_id: 'example_merchant_session_id'
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_reference_transaction_request,
+ 'Sale',
+ 2000,
+ {
+ reference_id: 'ref_id',
+ payment_type: 'Any',
+ invoice_id: 'invoice_id',
+ description: 'Description',
+ ip: '127.0.0.1',
+ merchant_session_id: 'example_merchant_session_id'
+ }
+ )
+ )
assert_equal '124', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text
assert_equal 'ref_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text
@@ -572,14 +614,20 @@ def test_build_reference_transaction_test
def test_build_reference_transaction_without_merchant_session_test
PaypalExpressGateway.application_id = 'ActiveMerchant_FOO'
- xml = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Sale', 2000,
- {
- reference_id: 'ref_id',
- payment_type: 'Any',
- invoice_id: 'invoice_id',
- description: 'Description',
- ip: '127.0.0.1'
- }))
+ xml = REXML::Document.new(
+ @gateway.send(
+ :build_reference_transaction_request,
+ 'Sale',
+ 2000,
+ {
+ reference_id: 'ref_id',
+ payment_type: 'Any',
+ invoice_id: 'invoice_id',
+ description: 'Description',
+ ip: '127.0.0.1'
+ }
+ )
+ )
assert_equal '124', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text
assert_equal 'ref_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text
@@ -633,26 +681,20 @@ def test_reference_transaction_requires_fields
def test_error_code_for_single_error
@gateway.expects(:ssl_post).returns(response_with_error)
- response = @gateway.setup_authorization(100,
- return_url: 'http://example.com',
- cancel_return_url: 'http://example.com')
+ response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com')
assert_equal '10736', response.params['error_codes']
end
def test_ensure_only_unique_error_codes
@gateway.expects(:ssl_post).returns(response_with_duplicate_errors)
- response = @gateway.setup_authorization(100,
- return_url: 'http://example.com',
- cancel_return_url: 'http://example.com')
+ response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com')
assert_equal '10736', response.params['error_codes']
end
def test_error_codes_for_multiple_errors
@gateway.expects(:ssl_post).returns(response_with_errors)
- response = @gateway.setup_authorization(100,
- return_url: 'http://example.com',
- cancel_return_url: 'http://example.com')
+ response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com')
assert_equal %w[10736 10002], response.params['error_codes'].split(',')
end
diff --git a/test/unit/gateways/paypal_test.rb b/test/unit/gateways/paypal_test.rb
index a7165ef3b42..db9f5c760a0 100644
--- a/test/unit/gateways/paypal_test.rb
+++ b/test/unit/gateways/paypal_test.rb
@@ -260,21 +260,29 @@ def test_button_source_via_credentials_with_no_application_id
end
def test_item_total_shipping_handling_and_tax_not_included_unless_all_are_present
- xml = @gateway.send(:build_sale_or_authorization_request, 'Authorization', @amount, @credit_card,
+ xml = @gateway.send(
+ :build_sale_or_authorization_request,
+ 'Authorization', @amount, @credit_card,
tax: @amount,
shipping: @amount,
- handling: @amount)
+ handling: @amount
+ )
doc = REXML::Document.new(xml)
assert_nil REXML::XPath.first(doc, '//n2:PaymentDetails/n2:TaxTotal')
end
def test_item_total_shipping_handling_and_tax
- xml = @gateway.send(:build_sale_or_authorization_request, 'Authorization', @amount, @credit_card,
+ xml = @gateway.send(
+ :build_sale_or_authorization_request,
+ 'Authorization',
+ @amount,
+ @credit_card,
tax: @amount,
shipping: @amount,
handling: @amount,
- subtotal: 200)
+ subtotal: 200
+ )
doc = REXML::Document.new(xml)
assert_equal '1.00', REXML::XPath.first(doc, '//n2:PaymentDetails/n2:TaxTotal').text
diff --git a/test/unit/gateways/payway_test.rb b/test/unit/gateways/payway_test.rb
index 14ccfe87ff0..f5959f9b0fd 100644
--- a/test/unit/gateways/payway_test.rb
+++ b/test/unit/gateways/payway_test.rb
@@ -11,7 +11,7 @@ def setup
@amount = 1000
@credit_card = ActiveMerchant::Billing::CreditCard.new(
- number: 4564710000000004,
+ number: '4564710000000004',
month: 2,
year: 2019,
first_name: 'Bob',
diff --git a/test/unit/gateways/realex_test.rb b/test/unit/gateways/realex_test.rb
index 84f7da6bd99..9fc3358c256 100644
--- a/test/unit/gateways/realex_test.rb
+++ b/test/unit/gateways/realex_test.rb
@@ -5,8 +5,7 @@ class RealexTest < Test::Unit::TestCase
class ActiveMerchant::Billing::RealexGateway
# For the purposes of testing, lets redefine some protected methods as public.
- public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request,
- :build_capture_request, :build_verify_request, :build_credit_request
+ public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request, :build_capture_request, :build_verify_request, :build_credit_request
end
def setup
diff --git a/test/unit/gateways/sage_pay_test.rb b/test/unit/gateways/sage_pay_test.rb
index 6abd0e9e3f9..8f915a1f6c4 100644
--- a/test/unit/gateways/sage_pay_test.rb
+++ b/test/unit/gateways/sage_pay_test.rb
@@ -316,9 +316,7 @@ def test_successful_authorization_and_capture_and_refund
assert_success capture
refund = stub_comms do
- @gateway.refund(@amount, capture.authorization,
- order_id: generate_unique_id,
- description: 'Refund txn')
+ @gateway.refund(@amount, capture.authorization, order_id: generate_unique_id, description: 'Refund txn')
end.respond_with(successful_refund_response)
assert_success refund
end
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 5c25ff7809b..df3abed1f71 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -11,10 +11,12 @@ def setup
@visa_token = 'pm_card_visa'
@three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession'
- @three_ds_off_session_credit_card = credit_card('4000002500003155',
+ @three_ds_off_session_credit_card = credit_card(
+ '4000002500003155',
verification_value: '737',
month: 10,
- year: 2022)
+ year: 2022
+ )
@amount = 2020
@update_amount = 2050
@@ -426,7 +428,7 @@ def test_succesful_purchase_with_stored_credentials_without_optional_ds_transact
confirm: true,
off_session: true,
stored_credential: {
- network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/
+ network_transaction_id: network_transaction_id # TEST env seems happy with any value :/
}
})
end.check_request do |_method, _endpoint, data, _headers|
@@ -526,7 +528,6 @@ def test_purchase_with_shipping_options
def test_purchase_with_shipping_carrier_and_tracking_number
options = {
currency: 'GBP',
- customer: @customer,
shipping_address: {
name: 'John Adam',
address1: 'block C'
@@ -534,6 +535,7 @@ def test_purchase_with_shipping_carrier_and_tracking_number
shipping_tracking_number: 'TXNABC123',
shipping_carrier: 'FEDEX'
}
+ options[:customer] = @customer if defined?(@customer)
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @visa_token, options)
end.check_request do |_method, _endpoint, data, _headers|
diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb
index deaee1aea6d..1ce0e52c96c 100644
--- a/test/unit/gateways/stripe_test.rb
+++ b/test/unit/gateways/stripe_test.rb
@@ -966,10 +966,12 @@ def test_add_creditcard_with_emv_credit_card
def test_add_creditcard_pads_eci_value
post = {}
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: '111111111100cryptogram',
verification_value: nil,
- eci: '7')
+ eci: '7'
+ )
@gateway.send(:add_creditcard, post, credit_card, {})
@@ -1440,10 +1442,12 @@ def test_successful_auth_with_network_tokenization_apple_pay
true
end.returns(successful_authorization_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: '111111111100cryptogram',
verification_value: nil,
- eci: '05')
+ eci: '05'
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_instance_of Response, response
@@ -1460,11 +1464,13 @@ def test_successful_auth_with_network_tokenization_android_pay
true
end.returns(successful_authorization_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: '111111111100cryptogram',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_instance_of Response, response
@@ -1481,10 +1487,12 @@ def test_successful_purchase_with_network_tokenization_apple_pay
true
end.returns(successful_authorization_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: '111111111100cryptogram',
verification_value: nil,
- eci: '05')
+ eci: '05'
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_instance_of Response, response
@@ -1501,11 +1509,13 @@ def test_successful_purchase_with_network_tokenization_android_pay
true
end.returns(successful_authorization_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: '111111111100cryptogram',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_instance_of Response, response
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index afb7b49bb57..a256785be9d 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -12,27 +12,35 @@ def setup
@amount = 100
@credit_card = credit_card('4242424242424242')
@token = '|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8'
- @elo_credit_card = credit_card('4514 1600 0000 0008',
+ @elo_credit_card = credit_card(
+ '4514 1600 0000 0008',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
- @nt_credit_card = network_tokenization_credit_card('4895370015293175',
+ brand: 'elo'
+ )
+ @nt_credit_card = network_tokenization_credit_card(
+ '4895370015293175',
brand: 'visa',
eci: 5,
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @nt_credit_card_without_eci = network_tokenization_credit_card('4895370015293175',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @nt_credit_card_without_eci = network_tokenization_credit_card(
+ '4895370015293175',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @credit_card_with_two_digits_year = credit_card('4514 1600 0000 0008',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @credit_card_with_two_digits_year = credit_card(
+ '4514 1600 0000 0008',
month: 10,
year: 22,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
+ verification_value: '737'
+ )
@sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
@options = { order_id: 1 }
@store_options = {
@@ -47,21 +55,25 @@ def setup
}
}
- @apple_play_network_token = network_tokenization_credit_card('4895370015293175',
+ @apple_play_network_token = network_tokenization_credit_card(
+ '4895370015293175',
month: 10,
year: 24,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
@level_two_data = {
level_2_data: {
@@ -664,9 +676,7 @@ def test_capture_time
end.check_request do |_endpoint, data, _headers|
if /capture/.match?(data)
t = Time.now
- assert_tag_with_attributes 'date',
- { 'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s },
- data
+ assert_tag_with_attributes 'date', { 'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s }, data
end
end.respond_with(successful_inquiry_response, successful_capture_response)
end
@@ -675,9 +685,7 @@ def test_amount_handling
stub_comms do
@gateway.authorize(100, @credit_card, @options)
end.check_request do |_endpoint, data, _headers|
- assert_tag_with_attributes 'amount',
- { 'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP' },
- data
+ assert_tag_with_attributes 'amount', { 'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP' }, data
end.respond_with(successful_authorize_response)
end
@@ -685,17 +693,13 @@ def test_currency_exponent_handling
stub_comms do
@gateway.authorize(10000, @credit_card, @options.merge(currency: :JPY))
end.check_request do |_endpoint, data, _headers|
- assert_tag_with_attributes 'amount',
- { 'value' => '100', 'exponent' => '0', 'currencyCode' => 'JPY' },
- data
+ assert_tag_with_attributes 'amount', { 'value' => '100', 'exponent' => '0', 'currencyCode' => 'JPY' }, data
end.respond_with(successful_authorize_response)
stub_comms do
@gateway.authorize(10000, @credit_card, @options.merge(currency: :OMR))
end.check_request do |_endpoint, data, _headers|
- assert_tag_with_attributes 'amount',
- { 'value' => '10000', 'exponent' => '3', 'currencyCode' => 'OMR' },
- data
+ assert_tag_with_attributes 'amount', { 'value' => '10000', 'exponent' => '3', 'currencyCode' => 'OMR' }, data
end.respond_with(successful_authorize_response)
end
From 3f45193329288ba6e7ba110dc18641dbc4c8177a Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 25 Jul 2023 08:57:43 -0500
Subject: [PATCH 124/390] Release v1.134.0
---
CHANGELOG | 7 +++++--
lib/active_merchant/version.rb | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 792c8c2586e..d304a0625c5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,11 +2,14 @@
= ActiveMerchant CHANGELOG
== HEAD
-* Paysafe: Map order_id to merchantRefNum [jcreiff] #4839
-* Stripe PI: Gate sending NTID [almalee24] #4828
+
+== Version 1.134.0 (July 25, 2023)
+* Update required Ruby version [almalee24] #4823
== Version 1.133.0 (July 20, 2023)
* CyberSource: remove credentials from tests [bbraschi] #4836
+* Paysafe: Map order_id to merchantRefNum [jcreiff] #4839
+* Stripe PI: Gate sending NTID [almalee24] #4828
== Version 1.132.0 (July 20, 2023)
* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index 89c8baee66b..4fa45514146 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.133.0'
+ VERSION = '1.134.0'
end
From d9c926af1a3972c25a5ef67b572b3c30d5a1a2af Mon Sep 17 00:00:00 2001
From: Johan Herrera
Date: Tue, 18 Jul 2023 16:48:16 -0500
Subject: [PATCH 125/390] Kushki: Enable 3ds2
Summary:
Enable 3ds version 2 on the gateway above
SER-625
Unit Test
Finished in 0.019977 seconds.
------------------------------------------------------------------------------------------------------
17 tests, 109 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------
Remote Test
Finished in 82.28609 seconds.
------------------------------------------------------------------------------------------------------
23 tests, 68 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------
---
CHANGELOG | 1 +
.../billing/gateways/kushki.rb | 40 +++++++++-
test/remote/gateways/remote_kushki_test.rb | 80 +++++++++++++++++++
3 files changed, 117 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d304a0625c5..9c63f8860df 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
+* Kushki: Enable 3ds2 [jherreraa] #4832
== Version 1.133.0 (July 20, 2023)
* CyberSource: remove credentials from tests [bbraschi] #4836
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index 9b3e726618c..67b781539b3 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -20,14 +20,14 @@ def initialize(options = {})
def purchase(amount, payment_method, options = {})
MultiResponse.run() do |r|
r.process { tokenize(amount, payment_method, options) }
- r.process { charge(amount, r.authorization, options) }
+ r.process { charge(amount, r.authorization, options, payment_method) }
end
end
def authorize(amount, payment_method, options = {})
MultiResponse.run() do |r|
r.process { tokenize(amount, payment_method, options) }
- r.process { preauthorize(amount, r.authorization, options) }
+ r.process { preauthorize(amount, r.authorization, options, payment_method) }
end
end
@@ -89,7 +89,7 @@ def tokenize(amount, payment_method, options)
commit(action, post)
end
- def charge(amount, authorization, options)
+ def charge(amount, authorization, options, payment_method = {})
action = 'charge'
post = {}
@@ -100,11 +100,12 @@ def charge(amount, authorization, options)
add_metadata(post, options)
add_months(post, options)
add_deferred(post, options)
+ add_three_d_secure(post, payment_method, options)
commit(action, post)
end
- def preauthorize(amount, authorization, options)
+ def preauthorize(amount, authorization, options, payment_method = {})
action = 'preAuthorization'
post = {}
@@ -114,6 +115,7 @@ def preauthorize(amount, authorization, options)
add_metadata(post, options)
add_months(post, options)
add_deferred(post, options)
+ add_three_d_secure(post, payment_method, options)
commit(action, post)
end
@@ -204,6 +206,36 @@ def add_deferred(post, options)
}
end
+ def add_three_d_secure(post, payment_method, options)
+ three_d_secure = options[:three_d_secure]
+ return unless three_d_secure.present?
+
+ post[:threeDomainSecure] = {
+ eci: three_d_secure[:eci],
+ specificationVersion: three_d_secure[:version]
+ }
+
+ if payment_method.brand == 'master'
+ post[:threeDomainSecure][:acceptRisk] = three_d_secure[:eci] == '00'
+ post[:threeDomainSecure][:ucaf] = three_d_secure[:cavv]
+ post[:threeDomainSecure][:directoryServerTransactionID] = three_d_secure[:ds_transaction_id]
+ case three_d_secure[:eci]
+ when '07'
+ post[:threeDomainSecure][:collectionIndicator] = '0'
+ when '06'
+ post[:threeDomainSecure][:collectionIndicator] = '1'
+ else
+ post[:threeDomainSecure][:collectionIndicator] = '2'
+ end
+ elsif payment_method.brand == 'visa'
+ post[:threeDomainSecure][:acceptRisk] = three_d_secure[:eci] == '07'
+ post[:threeDomainSecure][:cavv] = three_d_secure[:cavv]
+ post[:threeDomainSecure][:xid] = three_d_secure[:xid]
+ else
+ raise ArgumentError.new 'Kushki supports 3ds2 authentication for only Visa and Mastercard brands.'
+ end
+ end
+
ENDPOINT = {
'tokenize' => 'tokens',
'charge' => 'charges',
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index 7b84626e4f0..e310d6bb91e 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -179,6 +179,86 @@ def test_failed_authorize
assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message
end
+ def test_successful_3ds2_authorize_with_visa_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE=',
+ eci: '07'
+ }
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_3ds2_authorize_with_master_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ eci: '00',
+ ds_transaction_id: 'b23e0264-1209-41L6-Jca4-b82143c1a782'
+ }
+ }
+
+ credit_card = credit_card('5223450000000007', brand: 'master', verification_value: '777')
+ response = @gateway.authorize(@amount, credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_3ds2_purchase
+ options = {
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE=',
+ eci: '07'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_failed_3ds2_authorize
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ authentication_response_status: 'Y',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE='
+ }
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'K001', response.responses.last.error_code
+ end
+
+ def test_failed_3ds2_authorize_with_different_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE='
+ }
+ }
+ credit_card = credit_card('6011111111111117', brand: 'discover', verification_value: '777')
+ assert_raise ArgumentError do
+ @gateway.authorize(@amount, credit_card, options)
+ end
+ end
+
def test_successful_capture
auth = @gateway.authorize(@amount, @credit_card)
assert_success auth
From 413f4af271d1fe8476966afe718eaed0d57d0f8f Mon Sep 17 00:00:00 2001
From: Steve Hoeksema
Date: Sat, 8 Jul 2023 12:31:37 +1200
Subject: [PATCH 126/390] PaymentExpress: correct endpoints
Now that Payment Express has renamed to Windcave, the old endpoints no longer work.
Test Summary
Local: 5543 tests, 77553 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit: 37 tests, 263 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote: 17 tests, 79 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/payment_express.rb | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 9c63f8860df..75d903bef65 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* PaymentExpress: Correct endpoints [steveh] #4827
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb
index d61b5c11eef..51517b9277d 100644
--- a/lib/active_merchant/billing/gateways/payment_express.rb
+++ b/lib/active_merchant/billing/gateways/payment_express.rb
@@ -20,8 +20,8 @@ class PaymentExpressGateway < Gateway
self.homepage_url = 'https://www.windcave.com/'
self.display_name = 'Windcave (formerly PaymentExpress)'
- self.live_url = 'https://sec.paymentexpress.com/pxpost.aspx'
- self.test_url = 'https://uat.paymentexpress.com/pxpost.aspx'
+ self.live_url = 'https://sec.windcave.com/pxpost.aspx'
+ self.test_url = 'https://uat.windcave.com/pxpost.aspx'
APPROVED = '1'
From 104d8579a52de2101df555e52e1517b6895edf0a Mon Sep 17 00:00:00 2001
From: aenand
Date: Tue, 25 Jul 2023 15:37:00 -0400
Subject: [PATCH 127/390] Adyen: Support raw refusal reason
ECS-3082
A change was made two months ago to provide the MerchantAdviceCode
as the error message if available before falling back to the
refusalReasonRaw value. Some merchants do not want this change
in messaging so this gives them a way to elect which error
message they want
Test Summary
Remote:
11 failing tests on master
134 tests, 447 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.791% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 18 ++++++++++++++----
test/unit/gateways/adyen_test.rb | 9 +++++++++
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 75d903bef65..754f3f2db57 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
== HEAD
* PaymentExpress: Correct endpoints [steveh] #4827
+* Adyen: Add option to elect which error message [aenand] #4843
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index eb06a185379..0d93164e94f 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -706,7 +706,7 @@ def commit(action, parameters, options)
success = success_from(action, response, options)
Response.new(
success,
- message_from(action, response),
+ message_from(action, response, options),
response,
authorization: authorization_from(action, parameters, response),
test: test?,
@@ -776,13 +776,15 @@ def success_from(action, response, options)
end
end
- def message_from(action, response)
- return authorize_message_from(response) if %w(authorise authorise3d authorise3ds2).include?(action.to_s)
+ def message_from(action, response, options = {})
+ return authorize_message_from(response, options) if %w(authorise authorise3d authorise3ds2).include?(action.to_s)
response['response'] || response['message'] || response['result'] || response['resultCode']
end
- def authorize_message_from(response)
+ def authorize_message_from(response, options = {})
+ return raw_authorize_error_message(response) if options[:raw_error_message]
+
if response['refusalReason'] && response['additionalData'] && (response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw'])
"#{response['refusalReason']} | #{response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw']}"
else
@@ -790,6 +792,14 @@ def authorize_message_from(response)
end
end
+ def raw_authorize_error_message(response)
+ if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
+ "#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
+ else
+ response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
+ end
+ end
+
def authorization_from(action, parameters, response)
return nil if response['pspReference'].nil?
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index e37bb8accc0..18a4a48a0e4 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -354,6 +354,15 @@ def test_failed_authorise_mastercard
assert_failure response
end
+ def test_failed_authorise_mastercard_raw_error_message
+ @gateway.expects(:ssl_post).returns(failed_authorize_mastercard_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, { raw_error_message: true })
+
+ assert_equal 'Refused | 01: Refer to card issuer', response.message
+ assert_failure response
+ end
+
def test_successful_capture
@gateway.expects(:ssl_post).returns(successful_capture_response)
response = @gateway.capture(@amount, '7914775043909934')
From 9cfe650190c7818b00478b4ca608295d2a8b1897 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Tue, 25 Jul 2023 10:54:16 -0400
Subject: [PATCH 128/390] Reach: Update list of supported countries
Full list of countries supported is available in Reach docs: https://docs.withreach.com/v2.22/docs/currencies-and-countries
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/reach.rb | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 754f3f2db57..814fbc8ba7d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* PaymentExpress: Correct endpoints [steveh] #4827
* Adyen: Add option to elect which error message [aenand] #4843
+* Reach: Update list of supported countries [jcreiff] #4842
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/reach.rb b/lib/active_merchant/billing/gateways/reach.rb
index 092a19de698..41c2c6c9926 100644
--- a/lib/active_merchant/billing/gateways/reach.rb
+++ b/lib/active_merchant/billing/gateways/reach.rb
@@ -4,7 +4,14 @@ class ReachGateway < Gateway
self.test_url = 'https://checkout.rch.how/'
self.live_url = 'https://checkout.rch.io/'
- self.supported_countries = ['US']
+ self.supported_countries = %w(AE AG AL AM AT AU AW AZ BA BB BD BE BF BG BH BJ BM BN BO BR BS BW BZ CA CD CF
+ CH CI CL CM CN CO CR CU CV CY CZ DE DJ DK DM DO DZ EE EG ES ET FI FJ FK FR GA
+ GB GD GE GG GH GI GN GR GT GU GW GY HK HN HR HU ID IE IL IM IN IS IT JE JM JO
+ JP KE KG KH KM KN KR KW KY KZ LA LC LK LR LT LU LV LY MA MD MK ML MN MO MR MS
+ MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NZ OM PA PE PF PG PH PK PL PT PY
+ QA RO RS RW SA SB SC SE SG SH SI SK SL SN SO SR ST SV SY SZ TD TG TH TN TO TR
+ TT TV TW TZ UG US UY UZ VC VN VU WF WS YE ZM)
+
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa diners_club american_express jcb master discover maestro]
From 0ff6eaa3138cf2853ad0c4fdb4e7aaeb5a33b906 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Tue, 25 Jul 2023 09:22:10 -0400
Subject: [PATCH 129/390] Paysafe: Truncate address fields
Limits the length of address fields, according to Paysafe docs
---
CHANGELOG | 1 +
.../billing/gateways/paysafe.rb | 21 ++++++++++---------
test/remote/gateways/remote_paysafe_test.rb | 14 +++++++++++++
test/unit/gateways/paysafe_test.rb | 21 +++++++++++++++++++
4 files changed, 47 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 814fbc8ba7d..2142fc29de1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
* PaymentExpress: Correct endpoints [steveh] #4827
* Adyen: Add option to elect which error message [aenand] #4843
* Reach: Update list of supported countries [jcreiff] #4842
+* Paysafe: Truncate address fields [jcreiff] #4841
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/paysafe.rb b/lib/active_merchant/billing/gateways/paysafe.rb
index 1a35de0bb3a..a7cff9fe813 100644
--- a/lib/active_merchant/billing/gateways/paysafe.rb
+++ b/lib/active_merchant/billing/gateways/paysafe.rb
@@ -124,12 +124,13 @@ def add_billing_address(post, options)
return unless address = options[:billing_address] || options[:address]
post[:billingDetails] = {}
- post[:billingDetails][:street] = address[:address1]
- post[:billingDetails][:city] = address[:city]
- post[:billingDetails][:state] = address[:state]
+ post[:billingDetails][:street] = truncate(address[:address1], 50)
+ post[:billingDetails][:street2] = truncate(address[:address2], 50)
+ post[:billingDetails][:city] = truncate(address[:city], 40)
+ post[:billingDetails][:state] = truncate(address[:state], 40)
post[:billingDetails][:country] = address[:country]
- post[:billingDetails][:zip] = address[:zip]
- post[:billingDetails][:phone] = address[:phone]
+ post[:billingDetails][:zip] = truncate(address[:zip], 10)
+ post[:billingDetails][:phone] = truncate(address[:phone], 40)
end
# The add_address_for_vaulting method is applicable to the store method, as the APIs address
@@ -138,12 +139,12 @@ def add_address_for_vaulting(post, options)
return unless address = options[:billing_address] || options[:address]
post[:card][:billingAddress] = {}
- post[:card][:billingAddress][:street] = address[:address1]
- post[:card][:billingAddress][:street2] = address[:address2]
- post[:card][:billingAddress][:city] = address[:city]
- post[:card][:billingAddress][:zip] = address[:zip]
+ post[:card][:billingAddress][:street] = truncate(address[:address1], 50)
+ post[:card][:billingAddress][:street2] = truncate(address[:address2], 50)
+ post[:card][:billingAddress][:city] = truncate(address[:city], 40)
+ post[:card][:billingAddress][:zip] = truncate(address[:zip], 10)
post[:card][:billingAddress][:country] = address[:country]
- post[:card][:billingAddress][:state] = address[:state] if address[:state]
+ post[:card][:billingAddress][:state] = truncate(address[:state], 40) if address[:state]
end
# This data is specific to creating a profile at the gateway's vault level
diff --git a/test/remote/gateways/remote_paysafe_test.rb b/test/remote/gateways/remote_paysafe_test.rb
index c7943d6801a..72f5c0a3aae 100644
--- a/test/remote/gateways/remote_paysafe_test.rb
+++ b/test/remote/gateways/remote_paysafe_test.rb
@@ -148,6 +148,20 @@ def test_successful_purchase_with_airline_details
assert_equal 'F', response.params['airlineTravelDetails']['tripLegs']['leg2']['serviceClass']
end
+ def test_successful_purchase_with_truncated_address
+ options = {
+ billing_address: {
+ address1: "This is an extremely long address, it is unreasonably long and we can't allow it.",
+ address2: "This is an extremely long address2, it is unreasonably long and we can't allow it.",
+ city: 'Lake Chargoggagoggmanchauggagoggchaubunagungamaugg',
+ state: 'NC',
+ zip: '27701'
+ }
+ }
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
def test_successful_purchase_with_token
response = @gateway.purchase(200, @pm_token, @options)
assert_success response
diff --git a/test/unit/gateways/paysafe_test.rb b/test/unit/gateways/paysafe_test.rb
index 1b4302872e9..2d7c73d90ec 100644
--- a/test/unit/gateways/paysafe_test.rb
+++ b/test/unit/gateways/paysafe_test.rb
@@ -263,6 +263,27 @@ def test_merchant_ref_num_and_order_id
assert_success response
end
+ def test_truncate_long_address_fields
+ options = {
+ billing_address: {
+ address1: "This is an extremely long address, it is unreasonably long and we can't allow it.",
+ address2: "This is an extremely long address2, it is unreasonably long and we can't allow it.",
+ city: 'Lake Chargoggagoggmanchauggagoggchaubunagungamaugg',
+ state: 'NC',
+ zip: '27701'
+ }
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"street":"This is an extremely long address, it is unreasona"/, data)
+ assert_match(/"street2":"This is an extremely long address2, it is unreason"/, data)
+ assert_match(/"city":"Lake Chargoggagoggmanchauggagoggchaubuna"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_scrub
assert @gateway.supports_scrubbing?
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
From 23a917ced0c6f1180dc8fa191cb4e9f63d652b1b Mon Sep 17 00:00:00 2001
From: aenand
Date: Thu, 11 May 2023 15:31:13 -0400
Subject: [PATCH 130/390] BT: Add support for Network Tokens
ECS-2899
Braintree supports bring your own Network Tokens via their SDK
starting in Major version 4.0. This commit adds logic to update the BT gem
version used by the CI/CD tests to 4.0 and adds support for using Network
Tokens on the Braintree Blue gateway.
Remote Tests:
104 tests, 554 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remove android pay from BT
---
CHANGELOG | 1 +
Gemfile | 2 +-
.../billing/gateways/braintree_blue.rb | 104 ++++++++++++------
.../gateways/remote_braintree_blue_test.rb | 32 +++---
test/unit/gateways/braintree_blue_test.rb | 31 +++---
5 files changed, 97 insertions(+), 73 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 2142fc29de1..c10fef2ab21 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@
* Adyen: Add option to elect which error message [aenand] #4843
* Reach: Update list of supported countries [jcreiff] #4842
* Paysafe: Truncate address fields [jcreiff] #4841
+* Braintree: Support third party Network Tokens [aenand] #4775
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/Gemfile b/Gemfile
index 01084b00034..f653ef90ec3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,7 @@ gem 'rubocop', '~> 0.72.0', require: false
group :test, :remote_test do
# gateway-specific dependencies, keeping these gems out of the gemspec
- gem 'braintree', '>= 3.0.0', '<= 3.0.1'
+ gem 'braintree', '>= 4.12.0'
gem 'jose', '~> 1.1.3'
gem 'jwe'
gem 'mechanize'
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 3cbe60d309f..0bc075e35af 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -835,54 +835,86 @@ def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
def add_payment_method(parameters, credit_card_or_vault_id, options)
if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer)
- if options[:payment_method_token]
- parameters[:payment_method_token] = credit_card_or_vault_id
- options.delete(:billing_address)
- elsif options[:payment_method_nonce]
- parameters[:payment_method_nonce] = credit_card_or_vault_id
- else
- parameters[:customer_id] = credit_card_or_vault_id
- end
+ add_third_party_token(parameters, credit_card_or_vault_id, options)
else
parameters[:customer].merge!(
first_name: credit_card_or_vault_id.first_name,
last_name: credit_card_or_vault_id.last_name
)
if credit_card_or_vault_id.is_a?(NetworkTokenizationCreditCard)
- if credit_card_or_vault_id.source == :apple_pay
- parameters[:apple_pay_card] = {
- number: credit_card_or_vault_id.number,
- expiration_month: credit_card_or_vault_id.month.to_s.rjust(2, '0'),
- expiration_year: credit_card_or_vault_id.year.to_s,
- cardholder_name: credit_card_or_vault_id.name,
- cryptogram: credit_card_or_vault_id.payment_cryptogram,
- eci_indicator: credit_card_or_vault_id.eci
- }
- elsif credit_card_or_vault_id.source == :android_pay || credit_card_or_vault_id.source == :google_pay
- Braintree::Version::Major < 3 ? pay_card = :android_pay_card : pay_card = :google_pay_card
- parameters[pay_card] = {
- number: credit_card_or_vault_id.number,
- cryptogram: credit_card_or_vault_id.payment_cryptogram,
- expiration_month: credit_card_or_vault_id.month.to_s.rjust(2, '0'),
- expiration_year: credit_card_or_vault_id.year.to_s,
- google_transaction_id: credit_card_or_vault_id.transaction_id,
- source_card_type: credit_card_or_vault_id.brand,
- source_card_last_four: credit_card_or_vault_id.last_digits,
- eci_indicator: credit_card_or_vault_id.eci
- }
+ case credit_card_or_vault_id.source
+ when :apple_pay
+ add_apple_pay(parameters, credit_card_or_vault_id)
+ when :google_pay
+ add_google_pay(parameters, credit_card_or_vault_id)
+ else
+ add_network_tokenization_card(parameters, credit_card_or_vault_id)
end
else
- parameters[:credit_card] = {
- number: credit_card_or_vault_id.number,
- cvv: credit_card_or_vault_id.verification_value,
- expiration_month: credit_card_or_vault_id.month.to_s.rjust(2, '0'),
- expiration_year: credit_card_or_vault_id.year.to_s,
- cardholder_name: credit_card_or_vault_id.name
- }
+ add_credit_card(parameters, credit_card_or_vault_id)
end
end
end
+ def add_third_party_token(parameters, payment_method, options)
+ if options[:payment_method_token]
+ parameters[:payment_method_token] = payment_method
+ options.delete(:billing_address)
+ elsif options[:payment_method_nonce]
+ parameters[:payment_method_nonce] = payment_method
+ else
+ parameters[:customer_id] = payment_method
+ end
+ end
+
+ def add_credit_card(parameters, payment_method)
+ parameters[:credit_card] = {
+ number: payment_method.number,
+ cvv: payment_method.verification_value,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name
+ }
+ end
+
+ def add_apple_pay(parameters, payment_method)
+ parameters[:apple_pay_card] = {
+ number: payment_method.number,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name,
+ cryptogram: payment_method.payment_cryptogram,
+ eci_indicator: payment_method.eci
+ }
+ end
+
+ def add_google_pay(parameters, payment_method)
+ Braintree::Version::Major < 3 ? pay_card = :android_pay_card : pay_card = :google_pay_card
+ parameters[pay_card] = {
+ number: payment_method.number,
+ cryptogram: payment_method.payment_cryptogram,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ google_transaction_id: payment_method.transaction_id,
+ source_card_type: payment_method.brand,
+ source_card_last_four: payment_method.last_digits,
+ eci_indicator: payment_method.eci
+ }
+ end
+
+ def add_network_tokenization_card(parameters, payment_method)
+ parameters[:credit_card] = {
+ number: payment_method.number,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name,
+ network_tokenization_attributes: {
+ cryptogram: payment_method.payment_cryptogram,
+ ecommerce_indicator: payment_method.eci
+ }
+ }
+ end
+
def bank_account_errors(payment_method, options)
if payment_method.validate.present?
payment_method.validate
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 0ec9a6c33f7..3cc5b8cb5c9 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -31,6 +31,12 @@ def setup
},
ach_mandate: ach_mandate
}
+
+ @nt_credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :network_token,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
end
def test_credit_card_details_on_store
@@ -54,6 +60,13 @@ def test_successful_authorize
assert_equal 'authorized', response.params['braintree_transaction']['status']
end
+ def test_successful_authorize_with_nt
+ assert response = @gateway.authorize(@amount, @nt_credit_card, @options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'authorized', response.params['braintree_transaction']['status']
+ end
+
def test_successful_authorize_with_nil_and_empty_billing_address_options
credit_card = credit_card('5105105105105100')
options = {
@@ -682,25 +695,6 @@ def test_authorize_and_capture_with_apple_pay_card
assert_success capture
end
- def test_authorize_and_capture_with_android_pay_card
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- month: '01',
- year: '2024',
- source: :android_pay,
- transaction_id: '123456789',
- eci: '05'
- )
-
- assert auth = @gateway.authorize(@amount, credit_card, @options)
- assert_success auth
- assert_equal '1000 Approved', auth.message
- assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
- end
-
def test_authorize_and_capture_with_google_pay_card
credit_card = network_tokenization_credit_card(
'4111111111111111',
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index 7a05622ddbe..da16bc25950 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -1011,7 +1011,7 @@ def test_apple_pay_card
assert_equal 'transaction_id', response.authorization
end
- def test_android_pay_card
+ def test_google_pay_card
Braintree::TransactionGateway.any_instance.expects(:sale).
with(
amount: '1.00',
@@ -1038,7 +1038,7 @@ def test_android_pay_card
brand: 'visa',
eci: '05',
payment_cryptogram: '111111111100cryptogram',
- source: :android_pay,
+ source: :google_pay,
transaction_id: '1234567890'
)
@@ -1046,7 +1046,7 @@ def test_android_pay_card
assert_equal 'transaction_id', response.authorization
end
- def test_google_pay_card
+ def test_network_token_card
Braintree::TransactionGateway.any_instance.expects(:sale).
with(
amount: '1.00',
@@ -1055,27 +1055,24 @@ def test_google_pay_card
first_name: 'Longbob', last_name: 'Longsen' },
options: { store_in_vault: false, submit_for_settlement: nil, hold_in_escrow: nil },
custom_fields: nil,
- google_pay_card: {
+ credit_card: {
number: '4111111111111111',
expiration_month: '09',
expiration_year: (Time.now.year + 1).to_s,
- cryptogram: '111111111100cryptogram',
- google_transaction_id: '1234567890',
- source_card_type: 'visa',
- source_card_last_four: '1111',
- eci_indicator: '05'
+ cardholder_name: 'Longbob Longsen',
+ network_tokenization_attributes: {
+ cryptogram: '111111111100cryptogram',
+ ecommerce_indicator: '05'
+ }
}
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram',
- source: :google_pay,
- transaction_id: '1234567890'
- )
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :network_token,
+ payment_cryptogram: '111111111100cryptogram')
response = @gateway.authorize(100, credit_card, test: true, order_id: '1')
assert_equal 'transaction_id', response.authorization
From 47f663be6ba8736c28a6f2a25913530042629293 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 31 Jul 2023 10:26:21 -0700
Subject: [PATCH 131/390] Kushki: fix add amount default method
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/kushki.rb | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c10fef2ab21..1cf653d022c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@
* Reach: Update list of supported countries [jcreiff] #4842
* Paysafe: Truncate address fields [jcreiff] #4841
* Braintree: Support third party Network Tokens [aenand] #4775
+* Kushki: Fix add amount default method for subtotalIva and subtotalIva0 [yunnydang] #4845
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index 67b781539b3..0bf56881cda 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -135,9 +135,9 @@ def add_invoice(action, post, money, options)
end
def add_amount_defaults(sum, money, options)
- sum[:subtotalIva] = amount(money).to_f
+ sum[:subtotalIva] = 0
sum[:iva] = 0
- sum[:subtotalIva0] = 0
+ sum[:subtotalIva0] = amount(money).to_f
sum[:ice] = 0 if sum[:currency] != 'COP'
end
From bb379176db12a181a961b21113f0ee7d220f06e1 Mon Sep 17 00:00:00 2001
From: aenand
Date: Fri, 21 Jul 2023 16:12:21 -0400
Subject: [PATCH 132/390] Rapyd: Add customer object and fix tests
The Rapyd gateway requires the Customer object on multiple payment
types now and is optional on all. This commit adds the customer subhash
to requests and updates the remote tests to pass since Rapyd has changed
it's requirements.
Remote:
31 tests, 88 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 47 +++++++++++++---
test/remote/gateways/remote_rapyd_test.rb | 55 +++++--------------
test/unit/gateways/rapyd_test.rb | 10 +---
4 files changed, 54 insertions(+), 59 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1cf653d022c..183c18ef217 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@
* Paysafe: Truncate address fields [jcreiff] #4841
* Braintree: Support third party Network Tokens [aenand] #4775
* Kushki: Fix add amount default method for subtotalIva and subtotalIva0 [yunnydang] #4845
+* Rapyd: Add customer object to requests [aenand] #4838
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 51b0fb326ce..9761ed7f642 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -63,7 +63,7 @@ def verify(credit_card, options = {})
def store(payment, options = {})
post = {}
add_payment(post, payment, options)
- add_customer_object(post, payment, options)
+ add_customer_data(post, payment, options, 'store')
add_metadata(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
@@ -100,14 +100,13 @@ def add_reference(authorization)
def add_auth_purchase(post, money, payment, options)
add_invoice(post, money, options)
add_payment(post, payment, options)
- add_customer_object(post, payment, options)
+ add_customer_data(post, payment, options)
add_3ds(post, payment, options)
add_address(post, payment, options)
add_metadata(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
add_payment_urls(post, options)
- add_customer_id(post, options)
end
def add_address(post, creditcard, options)
@@ -213,12 +212,42 @@ def add_payment_urls(post, options)
post[:error_payment_url] = options[:error_payment_url] if options[:error_payment_url]
end
- def add_customer_object(post, payment, options)
- post[:name] = "#{payment.first_name} #{payment.last_name}" unless payment.is_a?(String)
- phone = options.dig(:billing_address, :phone) .gsub(/\D/, '') unless options[:billing_address].nil?
- post[:phone_number] = phone || options.dig(:customer, :phone_number)
- post[:email] = options[:email] || options.dig(:customer, :email)
- post[:addresses] = options.dig(:customer, :addresses) if USA_PAYMENT_METHODS.include?(options[:pm_type])
+ def add_customer_data(post, payment, options, action = '')
+ post[:phone_number] = options.dig(:billing_address, :phone) .gsub(/\D/, '') unless options[:billing_address].nil?
+ post[:email] = options[:email]
+ return add_customer_id(post, options) if options[:customer_id]
+
+ if action == 'store'
+ post.merge!(customer_fields(payment, options))
+ else
+ post[:customer] = customer_fields(payment, options)
+ end
+ end
+
+ def customer_fields(payment, options)
+ return if options[:customer_id]
+
+ customer_data = {}
+ customer_data[:name] = "#{payment.first_name} #{payment.last_name}" unless payment.is_a?(String)
+ customer_data[:addresses] = [address(options)]
+ customer_data
+ end
+
+ def address(options)
+ return unless address = options[:billing_address]
+
+ formatted_address = {}
+
+ formatted_address[:name] = address[:name] if address[:name]
+ formatted_address[:line_1] = address[:address1] if address[:address1]
+ formatted_address[:line_2] = address[:address2] if address[:address2]
+ formatted_address[:city] = address[:city] if address[:city]
+ formatted_address[:state] = address[:state] if address[:state]
+ formatted_address[:country] = address[:country] if address[:country]
+ formatted_address[:zip] = address[:zip] if address[:zip]
+ formatted_address[:phone_number] = address[:phone].gsub(/\D/, '') if address[:phone]
+
+ formatted_address
end
def add_customer_id(post, options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index f2f961a7ed3..30009b817a4 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -22,7 +22,9 @@ def setup
pm_type: 'us_ach_bank',
currency: 'USD',
proof_of_authorization: false,
- payment_purpose: 'Testing Purpose'
+ payment_purpose: 'Testing Purpose',
+ email: 'test@example.com',
+ billing_address: address(name: 'Jim Reynolds')
}
@metadata = {
'array_of_objects': [
@@ -47,14 +49,7 @@ def setup
eci: '02'
}
- @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', phone_number: '12125559999')
-
- @customer_object = {
- name: 'John Doe',
- phone_number: '1234567890',
- email: 'est@example.com',
- addresses: [@address_object]
- }
+ @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', zip: '12345', name: 'john doe', phone_number: '12125559999')
end
def test_successful_purchase
@@ -63,25 +58,14 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
- def test_successful_authorize_with_customer_object
- @options[:customer] = @customer_object
+ def test_successful_authorize_with_mastercard
@options[:pm_type] = 'us_debit_mastercard_card'
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
- def test_successful_purchase_with_customer_object
- @options[:customer] = @customer_object
- @options[:pm_type] = 'us_debit_mastercard_card'
- response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'SUCCESS', response.message
- end
-
- def test_success_purchase_without_customer_fullname
- @credit_card.first_name = ''
- @credit_card.last_name = ''
+ def test_successful_purchase_with_mastercard
@options[:pm_type] = 'us_debit_mastercard_card'
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -96,12 +80,13 @@ def test_success_purchase_without_address_object_customer
end
def test_successful_subsequent_purchase_with_stored_credential
- @options[:currency] = 'EUR'
- @options[:pm_type] = 'gi_visa_card'
+ @options[:currency] = 'GBP'
+ @options[:pm_type] = 'gb_visa_card'
@options[:complete_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
@options[:error_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
- response = @gateway.purchase(15000, @credit_card, @options.merge({ stored_credential: { network_transaction_id: '123456', reason_type: 'recurring' } }))
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @options.merge({ stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' } }))
assert_success response
assert_equal 'SUCCESS', response.message
end
@@ -214,27 +199,15 @@ def test_failed_void
end
def test_successful_verify
- response = @gateway.verify(@credit_card, @options.except(:billing_address))
+ response = @gateway.verify(@credit_card, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
def test_successful_verify_with_peso
- options = {
- pm_type: 'mx_visa_card',
- currency: 'MXN'
- }
- response = @gateway.verify(@credit_card, options)
- assert_success response
- assert_equal 'SUCCESS', response.message
- end
-
- def test_successful_verify_with_yen
- options = {
- pm_type: 'jp_visa_card',
- currency: 'JPY'
- }
- response = @gateway.verify(@credit_card, options)
+ @options[:pm_type] = 'mx_visa_card'
+ @options[:currency] = 'MXN'
+ response = @gateway.verify(@credit_card, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index bab8839b14f..9fc082ee9b5 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -41,13 +41,6 @@ def setup
@ewallet_id = 'ewallet_1a867a32b47158b30a8c17d42f12f3f1'
@address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', phone_number: '12125559999')
-
- @customer_object = {
- name: 'John Doe',
- phone_number: '1234567890',
- email: 'est@example.com',
- addresses: [@address_object]
- }
end
def test_successful_purchase
@@ -227,14 +220,13 @@ def test_failed_purchase_without_customer_object
def test_successful_purchase_with_customer_object
stub_comms(@gateway, :ssl_request) do
- @options[:customer] = @customer_object
@options[:pm_type] = 'us_debit_mastercard_card'
@gateway.purchase(@amount, @credit_card, @options)
end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
assert_match(/"name":"Jim Reynolds"/, data)
assert_match(/"email":"test@example.com"/, data)
assert_match(/"phone_number":"5555555555"/, data)
- assert_match(/"address1":"456 My Street","address2":"Apt 1","company":"Widgets Inc","city":"Ottawa","state":"ON","zip":"K1C2N6","country":"CA"/, data)
+ assert_match(/"customer":/, data)
end
end
From 21d987dcaaa5d2401eaecf9430d707b157130584 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 28 Jul 2023 09:39:47 -0500
Subject: [PATCH 133/390] Cybersource: Add merchant_id
Set the merchantId filed to options[:merchant_id] instead of @options[:login]
if available.
Cybersource Unit:
136 tests, 641 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
CybersourceRest Unit:
30 tests, 144 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/cyber_source.rb | 2 +-
lib/active_merchant/billing/gateways/cyber_source_rest.rb | 6 +++---
test/unit/gateways/cyber_source_rest_test.rb | 5 +++--
test/unit/gateways/cyber_source_test.rb | 3 ++-
5 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 183c18ef217..de02f873158 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
* Braintree: Support third party Network Tokens [aenand] #4775
* Kushki: Fix add amount default method for subtotalIva and subtotalIva0 [yunnydang] #4845
* Rapyd: Add customer object to requests [aenand] #4838
+* CyberSource: Add merchant_id [almalee24] #4844
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index dce4ed14941..ec015454ddc 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -556,7 +556,7 @@ def add_line_item_data(xml, options)
end
def add_merchant_data(xml, options)
- xml.tag! 'merchantID', @options[:login]
+ xml.tag! 'merchantID', options[:merchant_id] || @options[:login]
xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id
xml.tag! 'clientLibrary', 'Ruby Active Merchant'
xml.tag! 'clientLibraryVersion', VERSION
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index b4e58bdd635..28c4d9d6f12 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -330,7 +330,7 @@ def commit(action, post, options = {})
add_reconciliation_id(post, options)
add_sec_code(post, options)
add_invoice_number(post, options)
- response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post)))
+ response = parse(ssl_post(url(action), post.to_json, auth_headers(action, options, post)))
Response.new(
success_from(response),
message_from(response),
@@ -396,14 +396,14 @@ def sign_payload(payload)
Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload))
end
- def auth_headers(action, post, http_method = 'post')
+ def auth_headers(action, options, post, http_method = 'post')
digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present?
date = Time.now.httpdate
{
'Accept' => 'application/hal+json;charset=utf-8',
'Content-Type' => 'application/json;charset=utf-8',
- 'V-C-Merchant-Id' => @options[:merchant_id],
+ 'V-C-Merchant-Id' => options[:merchant_id] || @options[:merchant_id],
'Date' => date,
'Host' => host,
'Signature' => get_http_signature(action, digest, http_method, date),
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index 6c9bc3091c5..f6ba1b40eba 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -204,9 +204,10 @@ def test_authorize_apple_pay_visa
def test_authorize_google_pay_master_card
stub_comms do
- @gateway.authorize(100, @google_pay_mc, @options)
- end.check_request do |_endpoint, data, _headers|
+ @gateway.authorize(100, @google_pay_mc, @options.merge(merchant_id: 'MerchantId'))
+ end.check_request do |_endpoint, data, headers|
request = JSON.parse(data)
+ assert_equal 'MerchantId', headers['V-C-Merchant-Id']
assert_equal '002', request['paymentInformation']['tokenizedCard']['type']
assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index b87a5b8a493..55505cc09a8 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -66,8 +66,9 @@ def test_successful_credit_card_purchase
def test_successful_purchase_with_other_tax_fields
stub_comms do
- @gateway.purchase(100, @credit_card, @options.merge(national_tax_indicator: 1, vat_tax_rate: 1.01))
+ @gateway.purchase(100, @credit_card, @options.merge!(national_tax_indicator: 1, vat_tax_rate: 1.01, merchant_id: 'MerchantId'))
end.check_request do |_endpoint, data, _headers|
+ assert_match(/MerchantId<\/merchantID>/, data)
assert_match(/\s+1.01<\/vatTaxRate>\s+1<\/nationalTaxIndicator>\s+<\/otherTax>/m, data)
end.respond_with(successful_purchase_response)
end
From 9fa4dce4589b3b71bf58dfc893bbda950cce1e30 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Tue, 1 Aug 2023 16:52:25 -0700
Subject: [PATCH 134/390] Wordline (formerly Global Collect): Add agent
numberic code and house number field
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateway.rb | 9 +++++++++
lib/active_merchant/billing/gateways/global_collect.rb | 7 +++++--
test/remote/gateways/remote_global_collect_test.rb | 3 ++-
test/unit/gateways/global_collect_test.rb | 8 +++++---
5 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index de02f873158..3d269c67c96 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@
* Kushki: Fix add amount default method for subtotalIva and subtotalIva0 [yunnydang] #4845
* Rapyd: Add customer object to requests [aenand] #4838
* CyberSource: Add merchant_id [almalee24] #4844
+* Global Collect: Add agent numeric code and house number field [yunnydang] #4847
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb
index 063f65b9b9d..2cbeca869a1 100644
--- a/lib/active_merchant/billing/gateway.rb
+++ b/lib/active_merchant/billing/gateway.rb
@@ -315,6 +315,15 @@ def split_names(full_name)
[first_name, last_name]
end
+ def split_address(full_address)
+ address_parts = (full_address || '').split
+ return [nil, nil] if address_parts.size == 0
+
+ number = address_parts.shift
+ street = address_parts.join(' ')
+ [number, street]
+ end
+
def requires!(hash, *params)
params.each do |param|
if param.is_a?(Array)
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index 7dc5ecb62cf..52b5409193e 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -138,6 +138,7 @@ def add_airline_data(post, options)
airline_data['isThirdParty'] = options[:airline_data][:is_third_party] if options[:airline_data][:is_third_party]
airline_data['issueDate'] = options[:airline_data][:issue_date] if options[:airline_data][:issue_date]
airline_data['merchantCustomerId'] = options[:airline_data][:merchant_customer_id] if options[:airline_data][:merchant_customer_id]
+ airline_data['agentNumericCode'] = options[:airline_data][:agent_numeric_code] if options[:airline_data][:agent_numeric_code]
airline_data['flightLegs'] = add_flight_legs(airline_options)
airline_data['passengers'] = add_passengers(airline_options)
@@ -347,7 +348,8 @@ def add_address(post, creditcard, options)
shipping_address = options[:shipping_address]
if billing_address = options[:billing_address] || options[:address]
post['order']['customer']['billingAddress'] = {
- 'street' => truncate(billing_address[:address1], 50),
+ 'street' => truncate(split_address(billing_address[:address1])[1], 50),
+ 'houseNumber' => split_address(billing_address[:address1])[0],
'additionalInfo' => truncate(billing_address[:address2], 50),
'zip' => billing_address[:zip],
'city' => billing_address[:city],
@@ -357,7 +359,8 @@ def add_address(post, creditcard, options)
end
if shipping_address
post['order']['customer']['shippingAddress'] = {
- 'street' => truncate(shipping_address[:address1], 50),
+ 'street' => truncate(split_address(shipping_address[:address1])[1], 50),
+ 'houseNumber' => split_address(shipping_address[:address1])[0],
'additionalInfo' => truncate(shipping_address[:address2], 50),
'zip' => shipping_address[:zip],
'city' => shipping_address[:city],
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index e37e835a010..cd8efed3c02 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -278,6 +278,7 @@ def test_successful_purchase_with_airline_data
is_third_party: 'true',
issue_date: 'tday',
merchant_customer_id: 'MIDs',
+ agent_numeric_code: '12345',
passengers: [
{ first_name: 'Randi',
surname: 'Smith',
@@ -416,7 +417,7 @@ def test_successful_purchase_with_payment_product_id
assert_equal 135, response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['paymentProductId']
end
- def test_successful_purchase_with_truncated_address
+ def test_successful_purchase_with_truncated_split_address
response = @gateway.purchase(@amount, @credit_card, @long_address)
assert_success response
assert_equal 'Succeeded', response.message
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index efb0e69089e..537d233f5cc 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -238,6 +238,7 @@ def test_successful_purchase_airline_fields
name: 'Spreedly Airlines',
flight_date: '20190810',
passenger_name: 'Randi Smith',
+ agent_numeric_code: '12345',
flight_legs: [
{ arrival_airport: 'BDL',
origin_airport: 'RDU',
@@ -388,7 +389,7 @@ def test_successful_authorization_with_extra_options
end.check_request do |_method, _endpoint, data, _headers|
assert_match %r("fraudFields":{"website":"www.example.com","giftMessage":"Happy Day!","customerIpAddress":"127.0.0.1"}), data
assert_match %r("merchantReference":"123"), data
- assert_match %r("customer":{"personalInformation":{"name":{"firstName":"Longbob","surname":"Longsen"}},"merchantCustomerId":"123987","contactDetails":{"emailAddress":"example@example.com","phoneNumber":"\(555\)555-5555"},"billingAddress":{"street":"456 My Street","additionalInfo":"Apt 1","zip":"K1C2N6","city":"Ottawa","state":"ON","countryCode":"CA"}}}), data
+ assert_match %r("customer":{"personalInformation":{"name":{"firstName":"Longbob","surname":"Longsen"}},"merchantCustomerId":"123987","contactDetails":{"emailAddress":"example@example.com","phoneNumber":"\(555\)555-5555"},"billingAddress":{"street":"My Street","houseNumber":"456","additionalInfo":"Apt 1","zip":"K1C2N6","city":"Ottawa","state":"ON","countryCode":"CA"}}}), data
assert_match %r("paymentProductId":"123ABC"), data
end.respond_with(successful_authorize_response)
@@ -455,7 +456,7 @@ def test_handles_blank_names
assert_success response
end
- def test_truncates_address_fields
+ def test_truncates_split_address_fields
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@accepted_amount, @credit_card, {
billing_address: {
@@ -468,7 +469,8 @@ def test_truncates_address_fields
}
})
end.check_request do |_method, _endpoint, data, _headers|
- refute_match(/Supercalifragilisticexpialidociousthiscantbemorethanfiftycharacters/, data)
+ assert_equal(JSON.parse(data)['order']['customer']['billingAddress']['houseNumber'], '1234')
+ assert_equal(JSON.parse(data)['order']['customer']['billingAddress']['street'], 'Supercalifragilisticexpialidociousthiscantbemoreth')
end.respond_with(successful_capture_response)
assert_success response
end
From 3eaa3ca08e4347f0312375a046c13c90e6d8ba6d Mon Sep 17 00:00:00 2001
From: khoi_nguyen_deepstack
Date: Fri, 14 Jul 2023 18:13:15 -0700
Subject: [PATCH 135/390] Adding deepstack gateway
Baseline for future store feature
Remove some comments
Updating urls
Added shipping
Remove some comments
Updating urls
Added shipping
Remove some comments
Updating urls
Added shipping
Remove some comments
Updating urls
Added shipping
Rubocop offense fixes
Fixing PR comments
Remove unused credentials
Added missing tests. Removed :sandbox for test?
Removing puts statements
Move set capture true to separate function + remove code comments
Update changelog
---
CHANGELOG | 2 +
.../billing/gateways/deepstack.rb | 382 ++++++++++++++++++
test/fixtures.yml | 5 +
test/remote/gateways/remote_deepstack_test.rb | 230 +++++++++++
test/unit/gateways/deepstack_test.rb | 284 +++++++++++++
5 files changed, 903 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/deepstack.rb
create mode 100644 test/remote/gateways/remote_deepstack_test.rb
create mode 100644 test/unit/gateways/deepstack_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index 3d269c67c96..c48e241e172 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,8 @@
* Rapyd: Add customer object to requests [aenand] #4838
* CyberSource: Add merchant_id [almalee24] #4844
* Global Collect: Add agent numeric code and house number field [yunnydang] #4847
+* Deepstack: Add Deepstack Gateway [khoinguyendeepstack] #4830
+
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/deepstack.rb b/lib/active_merchant/billing/gateways/deepstack.rb
new file mode 100644
index 00000000000..796f3d601c2
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/deepstack.rb
@@ -0,0 +1,382 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DeepstackGateway < Gateway
+ self.test_url = 'https://api.sandbox.deepstack.io'
+ self.live_url = 'https://api.deepstack.io'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[visa master american_express discover]
+ self.money_format = :cents
+
+ self.homepage_url = 'https://deepstack.io/'
+ self.display_name = 'Deepstack Gateway'
+
+ STANDARD_ERROR_CODE_MAPPING = {}
+
+ def initialize(options = {})
+ requires!(options, :publishable_api_key, :app_id, :shared_secret)
+ @publishable_api_key, @app_id, @shared_secret = options.values_at(:publishable_api_key, :app_id, :shared_secret)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ add_payment(post, payment, options)
+ add_order(post, money, options)
+ add_purchase_capture(post)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_payment(post, payment, options)
+ add_order(post, money, options)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ commit('auth', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, authorization, options)
+
+ commit('capture', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, authorization, options)
+ commit('refund', post)
+ end
+
+ def void(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, authorization, options)
+ commit('void', post)
+ end
+
+ def verify(credit_card, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, credit_card, options) }
+ r.process(:ignore_result) { void(0, r.authorization, options) }
+ end
+ end
+
+ def get_token(credit_card, options = {})
+ post = {}
+ add_payment_instrument(post, credit_card, options)
+ add_address_payment_instrument(post, credit_card, options)
+ commit('gettoken', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]').
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((Hmac: )[\w=]+), '\1[FILTERED]').
+ gsub(%r((\\"account_number\\":\\")[\w*]+), '\1[FILTERED]').
+ gsub(%r((\\"cvv\\":\\")\w+), '\1[FILTERED]').
+ gsub(%r((\\"expiration\\":\\")\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post[:meta] ||= {}
+
+ add_shipping(post, options) if options.key?(:shipping_address)
+ post[:meta][:client_customer_id] = options[:customer] if options[:customer]
+ post[:meta][:client_transaction_id] = options[:order_id] if options[:order_id]
+ post[:meta][:client_transaction_description] = options[:description] if options[:description]
+ post[:meta][:client_invoice_id] = options[:invoice] if options[:invoice]
+ post[:meta][:card_holder_ip_address] = options[:ip] if options[:ip]
+ end
+
+ def add_address(post, creditcard, options)
+ return post unless options.key?(:address) || options.key?(:billing_address)
+
+ billing_address = options[:address] || options[:billing_address]
+ post[:source] ||= {}
+
+ post[:source][:billing_contact] = {}
+ post[:source][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
+ post[:source][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
+ post[:source][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
+ post[:source][:billing_contact][:email] = options[:email] if options[:email]
+ post[:source][:billing_contact][:address] = {}
+ post[:source][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
+ post[:source][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
+ post[:source][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
+ post[:source][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
+ post[:source][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
+ post[:source][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
+ end
+
+ def add_address_payment_instrument(post, creditcard, options)
+ return post unless options.key?(:address) || options.key?(:billing_address)
+
+ billing_address = options[:address] || options[:billing_address]
+ post[:source] = {} unless post.key?(:payment_instrument)
+
+ post[:payment_instrument][:billing_contact] = {}
+ post[:payment_instrument][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
+ post[:payment_instrument][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
+ post[:payment_instrument][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
+ post[:payment_instrument][:billing_contact][:email] = billing_address[:email] if billing_address[:email]
+ post[:payment_instrument][:billing_contact][:address] = {}
+ post[:payment_instrument][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
+ post[:payment_instrument][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
+ post[:payment_instrument][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
+ post[:payment_instrument][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
+ post[:payment_instrument][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
+ post[:payment_instrument][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
+ end
+
+ def add_shipping(post, options = {})
+ return post unless options.key?(:shipping_address)
+
+ shipping = options[:shipping_address]
+ post[:meta][:shipping_info] = {}
+ post[:meta][:shipping_info][:first_name] = shipping[:first_name] if shipping[:first_name]
+ post[:meta][:shipping_info][:last_name] = shipping[:last_name] if shipping[:last_name]
+ post[:meta][:shipping_info][:phone] = shipping[:phone] if shipping[:phone]
+ post[:meta][:shipping_info][:email] = shipping[:email] if shipping[:email]
+ post[:meta][:shipping_info][:address] = {}
+ post[:meta][:shipping_info][:address][:line_1] = shipping[:address1] if shipping[:address1]
+ post[:meta][:shipping_info][:address][:line_2] = shipping[:address2] if shipping[:address2]
+ post[:meta][:shipping_info][:address][:city] = shipping[:city] if shipping[:city]
+ post[:meta][:shipping_info][:address][:state] = shipping[:state] if shipping[:state]
+ post[:meta][:shipping_info][:address][:postal_code] = shipping[:zip] if shipping[:zip]
+ post[:meta][:shipping_info][:address][:country_code] = shipping[:country] if shipping[:country]
+ end
+
+ def add_invoice(post, money, authorization, options)
+ post[:amount] = amount(money)
+ post[:charge] = authorization
+ end
+
+ def add_payment(post, payment, options)
+ if payment.kind_of?(String)
+ post[:source] = {}
+ post[:source][:type] = 'card_on_file'
+ post[:source][:card_on_file] = {}
+ post[:source][:card_on_file][:id] = payment
+ post[:source][:card_on_file][:cvv] = options[:verification_value] || ''
+ post[:source][:card_on_file][:customer_id] = options[:customer_id] || ''
+ # credit card object
+ elsif payment.respond_to?(:number)
+ post[:source] = {}
+ post[:source][:type] = 'credit_card'
+ post[:source][:credit_card] = {}
+ post[:source][:credit_card][:account_number] = payment.number
+ post[:source][:credit_card][:cvv] = payment.verification_value || ''
+ post[:source][:credit_card][:expiration] = '%02d%02d' % [payment.month, payment.year % 100]
+ post[:source][:credit_card][:customer_id] = options[:customer_id] || ''
+ end
+ end
+
+ def add_payment_instrument(post, creditcard, options)
+ if creditcard.kind_of?(String)
+ post[:source] = creditcard
+ return post
+ end
+ return post unless creditcard.respond_to?(:number)
+
+ post[:payment_instrument] = {}
+ post[:payment_instrument][:type] = 'credit_card'
+ post[:payment_instrument][:credit_card] = {}
+ post[:payment_instrument][:credit_card][:account_number] = creditcard.number
+ post[:payment_instrument][:credit_card][:expiration] = '%02d%02d' % [creditcard.month, creditcard.year % 100]
+ post[:payment_instrument][:credit_card][:cvv] = creditcard.verification_value
+ end
+
+ def add_order(post, amount, options)
+ post[:transaction] ||= {}
+
+ post[:transaction][:amount] = amount
+ post[:transaction][:cof_type] = options.key?(:cof_type) ? options[:cof_type].upcase : 'UNSCHEDULED_CARDHOLDER'
+ post[:transaction][:capture] = false # Change this in the request (auth/charge)
+ post[:transaction][:currency_code] = (options[:currency] || currency(amount).upcase)
+ post[:transaction][:avs] = options[:avs] || true # default avs to true unless told otherwise
+ post[:transaction][:save_payment_instrument] = options[:save_payment_instrument] || false
+ end
+
+ def add_purchase_capture(post)
+ post[:transaction] ||= {}
+ post[:transaction][:capture] = true
+ end
+
+ def parse(body)
+ return {} if !body || body.empty?
+
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters, method = 'POST')
+ url = (test? ? test_url : live_url)
+ if no_hmac(action)
+ request_headers = headers.merge(create_basic(parameters, action))
+ else
+ request_headers = headers.merge(create_hmac(parameters, method))
+ end
+ request_url = url + get_url(action)
+ begin
+ response = parse(ssl_post(request_url, post_data(action, parameters), request_headers))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['avs_result']),
+ cvv_result: CVVResult.new(response['cvv_result']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ResponseError => e
+ Response.new(
+ false,
+ message_from_error(e.response.body),
+ response_error(e.response.body)
+ )
+ rescue JSON::ParserError
+ Response.new(
+ false,
+ message_from(response),
+ json_error(response)
+ )
+ end
+ end
+
+ def headers
+ {
+ 'Accept' => 'text/plain',
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def response_error(response)
+ parse(response)
+ rescue JSON::ParserError
+ json_error(response)
+ end
+
+ def json_error(response)
+ msg = 'Invalid response received from the Conekta API.'
+ msg += " (The raw response returned by the API was #{response.inspect})"
+ {
+ 'message' => msg
+ }
+ end
+
+ def success_from(response)
+ success = false
+ if response.key?('response_code')
+ success = response['response_code'] == '00'
+ # Hack because token/payment instrument methods do not return a response_code
+ elsif response.key?('id')
+ success = true if response['id'].start_with?('tok', 'card')
+ end
+
+ return success
+ end
+
+ def message_from(response)
+ response = JSON.parse(response) if response.is_a?(String)
+ if response.key?('message')
+ return response['message']
+ elsif response.key?('detail')
+ return response['detail']
+ end
+ end
+
+ def message_from_error(response)
+ if response.is_a?(String)
+ response.gsub!('\\"', '"')
+ response = JSON.parse(response)
+ end
+
+ if response.key?('detail')
+ return response['detail']
+ elsif response.key?('message')
+ return response['message']
+ end
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def post_data(action, parameters = {})
+ return JSON.generate(parameters)
+ end
+
+ def error_code_from(response)
+ error_code = nil
+ error_code = response['response_code'] unless success_from(response)
+ if error = response.dig('detail')
+ error_code = error
+ elsif error = response.dig('error')
+ error_code = error.dig('reason', 'id')
+ end
+ error_code
+ end
+
+ def get_url(action)
+ base = '/api/v1/'
+ case action
+ when 'sale'
+ return base + 'payments/charge'
+ when 'auth'
+ return base + 'payments/charge'
+ when 'capture'
+ return base + 'payments/capture'
+ when 'void'
+ return base + 'payments/refund'
+ when 'refund'
+ return base + 'payments/refund'
+ when 'gettoken'
+ return base + 'vault/token'
+ when 'vault'
+ return base + 'vault/payment-instrument/token'
+ else
+ return base + 'noaction'
+ end
+ end
+
+ def no_hmac(action)
+ case action
+ when 'gettoken'
+ return true
+ else
+ return false
+ end
+ end
+
+ def create_basic(post, method)
+ return { 'Authorization' => "Bearer #{@publishable_api_key}" }
+ end
+
+ def create_hmac(post, method)
+ # Need requestDate, requestMethod, Nonce, AppIDKey
+ app_id_key = @app_id
+ request_method = method.upcase
+ uuid = SecureRandom.uuid
+ request_time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
+
+ string_to_hash = "#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{JSON.generate(post)}"
+ signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), Base64.strict_decode64(@shared_secret), string_to_hash)
+ base64_signature = Base64.strict_encode64(signature)
+ hmac_header = Base64.strict_encode64("#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{base64_signature}")
+ return { 'hmac' => hmac_header }
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 049b6a2fbb8..2901eace4c0 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -308,6 +308,11 @@ decidir_plus_preauth:
decidir_purchase:
api_key: 5df6b5764c3f4822aecdc82d56f26b9d
+deepstack:
+ publishable_api_key: pk_test_7H5GkZJ4ktV38eZxKDItVMZZvluUhORE
+ app_id: afa25dea-58e3-4da0-a51e-bb6dca949c90
+ shared_secret: 30lYhQ92gREPrwaHOAkyubaIqi88wV8PYy4+2AWJ2to=
+
# No working test credentials
dibs:
merchant_id: SOMECREDENTIAL
diff --git a/test/remote/gateways/remote_deepstack_test.rb b/test/remote/gateways/remote_deepstack_test.rb
new file mode 100644
index 00000000000..f34a26960c2
--- /dev/null
+++ b/test/remote/gateways/remote_deepstack_test.rb
@@ -0,0 +1,230 @@
+require 'test_helper'
+
+class RemoteDeepstackTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+ @gateway = DeepstackGateway.new(fixtures(:deepstack))
+
+ @credit_card = credit_card
+ @amount = 100
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4111111111111111',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Bob',
+ last_name: 'Bobby'
+ )
+
+ @invalid_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '5146315000000051',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Failure',
+ last_name: 'Fail'
+ )
+
+ address = {
+ address1: '123 Some st',
+ address2: '',
+ first_name: 'Bob',
+ last_name: 'Bobberson',
+ city: 'Some City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ shipping_address = {
+ address1: '321 Some st',
+ address2: '#9',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ city: 'Other City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ shipping_address: shipping_address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_token
+ response = @gateway.get_token(@credit_card, @options)
+ assert_success response
+
+ sale = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success sale
+ assert_equal 'Approved', sale.message
+ end
+
+ def test_failed_token
+ response = @gateway.get_token(@invalid_card, @options)
+ assert_failure response
+ assert_equal 'InvalidRequestException: Card number is invalid.', response.message
+ end
+
+ # Feature currently gated. Will be released in future version
+ # def test_successful_vault
+
+ # response = @gateway.gettoken(@credit_card, @options)
+ # assert_success response
+
+ # vault = @gateway.store(response.authorization, @options)
+ # assert_success vault
+
+ # sale = @gateway.purchase(@amount, vault.authorization, @options)
+ # assert_success sale
+
+ # end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_true response.params['captured']
+ end
+
+ def test_successful_purchase_with_more_options
+ additional_options = {
+ ip: '127.0.0.1',
+ email: 'joe@example.com'
+ }
+
+ sent_options = @options.merge(additional_options)
+
+ response = @gateway.purchase(@amount, @credit_card, sent_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_not_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_not_equal 'Approved', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Current transaction does not exist or is in an invalid state.', response.message
+ end
+
+ # This test will always void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ # This test will always void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.params['id'])
+ assert_success refund
+ assert_equal @amount - 1, refund.params['amount']
+ end
+
+ # This test always be a void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Specified transaction does not exist.', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(0, auth.params['id'])
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void(0, '')
+ assert_failure response
+ assert_equal 'Specified transaction does not exist.', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Approved}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@invalid_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Request: Card number is invalid.}, response.message
+ end
+
+ def test_invalid_login
+ gateway = DeepstackGateway.new(publishable_api_key: '', app_id: '', shared_secret: '', sandbox: true)
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Specified transaction does not exist', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ expiration = '%02d%02d' % [@credit_card.month, @credit_card.year % 100]
+ assert_scrubbed(expiration, transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.get_token(@credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed('pk_test_XQS71KYAW9HW7XQOGAJIY4ENHZYZEO0C', transcript)
+ end
+end
diff --git a/test/unit/gateways/deepstack_test.rb b/test/unit/gateways/deepstack_test.rb
new file mode 100644
index 00000000000..c355a5e6a18
--- /dev/null
+++ b/test/unit/gateways/deepstack_test.rb
@@ -0,0 +1,284 @@
+require 'test_helper'
+
+class DeepstackTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+ @gateway = DeepstackGateway.new(fixtures(:deepstack))
+ @credit_card = credit_card
+ @amount = 100
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4111111111111111',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Bob',
+ last_name: 'Bobby'
+ )
+
+ address = {
+ address1: '123 Some st',
+ address2: '',
+ first_name: 'Bob',
+ last_name: 'Bobberson',
+ city: 'Some City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ email: 'test@test.com'
+ }
+
+ shipping_address = {
+ address1: '321 Some st',
+ address2: '#9',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ city: 'Other City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ shipping_address: shipping_address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_token
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+ response = @gateway.get_token(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_failed_token
+ @gateway.expects(:ssl_post).returns(failed_token_response)
+ response = @gateway.get_token(@credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert_equal 'ch_IoSx345fOU6SP67MRXgqWw', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'ch_vfndMRFdEUac0SnBNAAT6g', response.authorization
+ end
+
+ def test_failed_authorize
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_not_equal 'Approved', response.message
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+ response = @gateway.capture(@amount, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_post).returns(failed_capture_response)
+ response = @gateway.capture(@amount, '')
+
+ assert_failure response
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ response = @gateway.refund(@amount, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+ response = @gateway.refund(@amount, '')
+
+ assert_failure response
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+ response = @gateway.void(@amount, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_post).returns(failed_void_response)
+ response = @gateway.void(@amount, '')
+
+ assert_failure response
+ end
+
+ def test_successful_verify
+ @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response)
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_success response
+ end
+
+ def test_failed_verify
+ @gateway.expects(:ssl_request).returns(failed_authorize_response)
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_failure response
+ assert_match %r{Invalid Request: Card number is invalid.}, response.message
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ private
+
+ def pre_scrubbed
+ '
+ opening connection to api.sandbox.deepstack.io:443...
+ opened
+ starting SSL for api.sandbox.deepstack.io:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256
+ I, [2023-07-25T08:47:29.985581 #86287] INFO -- : [ActiveMerchant::Billing::DeepstackGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2023-07-25T08:47:29.985687 #86287] DEBUG -- : {"source":{"type":"credit_card","credit_card":{"account_number":"4111111111111111","cvv":"999","expiration":"0129","customer_id":""},"billing_contact":{"first_name":"Bob","last_name":"Bobberson","phone":"1231231234","address":{"line_1":"123 Some st","line_2":"","city":"Some City","state":"CA","postal_code":"12345","country_code":"USA"}}},"transaction":{"amount":100,"cof_type":"UNSCHEDULED_CARDHOLDER","capture":false,"currency_code":"USD","avs":true,"save_payment_instrument":false},"meta":{"shipping_info":{"first_name":"Jane","last_name":"Doe","phone":"1231231234","email":"test@test.com","address":{"line_1":"321 Some st","line_2":"#9","city":"Other City","state":"CA","postal_code":"12345","country_code":"USA"}},"client_transaction_id":"1","client_transaction_description":"Store Purchase"}}
+ <- "POST /api/v1/payments/charge HTTP/1.1\r\nContent-Type: application/json\r\nAccept: text/plain\r\nHmac: YWZhMjVkZWEtNThlMy00ZGEwLWE1MWUtYmI2ZGNhOTQ5YzkwfFBPU1R8MjAyMy0wNy0yNVQxNTo0NzoyOC43NzZafDIwMmIwZDJjLTdhZWMtNDk2Yy1hMTBlLWQ3ZDUzYTRhNTAzZHxpQmxXTFNNNFdjSjFkSGdlczJYb2JqWUpMVUlGM2tkeUg2b1RFbWtFRUVFPQ==\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: api.sandbox.deepstack.io\r\nContent-Length: 799\r\n\r\n"
+ <- "{\"source\":{\"type\":\"credit_card\",\"credit_card\":{\"account_number\":\"4111111111111111\",\"cvv\":\"999\",\"expiration\":\"0129\",\"customer_id\":\"\"},\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"phone\":\"1231231234\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}}},\"transaction\":{\"amount\":100,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"capture\":false,\"currency_code\":\"USD\",\"avs\":true,\"save_payment_instrument\":false},\"meta\":{\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"phone\":\"1231231234\",\"email\":\"test@test.com\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\"}}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Tue, 25 Jul 2023 15:47:30 GMT\r\n"
+ -> "Content-Type: application/json; charset=utf-8\r\n"
+ -> "Content-Length: 1389\r\n"
+ -> "Connection: close\r\n"
+ -> "server: Kestrel\r\n"
+ -> "apigw-requestid: IoI23jbrPHcESNQ=\r\n"
+ -> "api-supported-versions: 1.0\r\n"
+ -> "\r\n"
+ reading 1389 bytes...
+ -> "{\"id\":\"ch_gSuF1hGsU0CpPPAUs1dg-Q\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null},\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-25T15:47:30.183095Z\"}"
+ read 1389 bytes
+ Conn close
+ '
+ end
+
+ def post_scrubbed
+ '
+ opening connection to api.sandbox.deepstack.io:443...
+ opened
+ starting SSL for api.sandbox.deepstack.io:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256
+ I, [2023-07-25T08:47:29.985581 #86287] INFO -- : [ActiveMerchant::Billing::DeepstackGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2023-07-25T08:47:29.985687 #86287] DEBUG -- : {"source":{"type":"credit_card","credit_card":{"account_number":"4111111111111111","cvv":"999","expiration":"0129","customer_id":""},"billing_contact":{"first_name":"Bob","last_name":"Bobberson","phone":"1231231234","address":{"line_1":"123 Some st","line_2":"","city":"Some City","state":"CA","postal_code":"12345","country_code":"USA"}}},"transaction":{"amount":100,"cof_type":"UNSCHEDULED_CARDHOLDER","capture":false,"currency_code":"USD","avs":true,"save_payment_instrument":false},"meta":{"shipping_info":{"first_name":"Jane","last_name":"Doe","phone":"1231231234","email":"test@test.com","address":{"line_1":"321 Some st","line_2":"#9","city":"Other City","state":"CA","postal_code":"12345","country_code":"USA"}},"client_transaction_id":"1","client_transaction_description":"Store Purchase"}}
+ <- "POST /api/v1/payments/charge HTTP/1.1\r\nContent-Type: application/json\r\nAccept: text/plain\r\nHmac: [FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: api.sandbox.deepstack.io\r\nContent-Length: 799\r\n\r\n"
+ <- "{\"source\":{\"type\":\"credit_card\",\"credit_card\":{\"account_number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiration\":\"[FILTERED]\",\"customer_id\":\"\"},\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"phone\":\"1231231234\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}}},\"transaction\":{\"amount\":100,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"capture\":false,\"currency_code\":\"USD\",\"avs\":true,\"save_payment_instrument\":false},\"meta\":{\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"phone\":\"1231231234\",\"email\":\"test@test.com\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\"}}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Tue, 25 Jul 2023 15:47:30 GMT\r\n"
+ -> "Content-Type: application/json; charset=utf-8\r\n"
+ -> "Content-Length: 1389\r\n"
+ -> "Connection: close\r\n"
+ -> "server: Kestrel\r\n"
+ -> "apigw-requestid: IoI23jbrPHcESNQ=\r\n"
+ -> "api-supported-versions: 1.0\r\n"
+ -> "\r\n"
+ reading 1389 bytes...
+ -> "{\"id\":\"ch_gSuF1hGsU0CpPPAUs1dg-Q\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"[FILTERED]\",\"expiration\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null},\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-25T15:47:30.183095Z\"}"
+ read 1389 bytes
+ Conn close
+ '
+ end
+
+ def successful_purchase_response_2
+ %(
+ Easy to capture by setting the DEBUG_ACTIVE_MERCHANT environment variable
+ to "true" when running remote tests:
+
+ $ DEBUG_ACTIVE_MERCHANT=true ruby -Itest \
+ test/remote/gateways/remote_deepstack_test.rb \
+ -n test_successful_purchase
+ )
+ end
+
+ def successful_purchase_response
+ %({\"id\":\"ch_IoSx345fOU6SP67MRXgqWw\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":true,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-14T17:08:33.5004521Z\"})
+ end
+
+ def failed_purchase_response
+ %({\"id\":\"ch_xbaPjifXN0Gum4vzdup6iA\",\"response_code\":\"03\",\"message\":\"Invalid Request: Card number is invalid.\",\"approved\":false,\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************0051\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"MasterCard\",\"last_four\":\"0051\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":null,\"address_postal_code_check\":null,\"cvc_check\":null},\"completed\":\"2023-07-14T17:11:24.972201Z\"})
+ end
+
+ def successful_authorize_response
+ %({\"id\":\"ch_vfndMRFdEUac0SnBNAAT6g\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-14T17:36:18.4817926Z\"})
+ end
+
+ def failed_authorize_response
+ %({\"id\":\"ch_CBue2iT3pUibJ7QySysTrA\",\"response_code\":\"03\",\"message\":\"Invalid Request: Card number is invalid.\",\"approved\":false,\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************0051\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"MasterCard\",\"last_four\":\"0051\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":null,\"address_postal_code_check\":null,\"cvc_check\":null},\"completed\":\"2023-07-14T17:42:30.1835831Z\"})
+ end
+
+ def successful_capture_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_KpmspGEiSUCgavxiE-xPTw\",\"amount\":100,\"recurring\":false,\"completed\":\"2023-07-14T19:58:49.3255779+00:00\"})
+ end
+
+ def failed_capture_response
+ %({\"response_code\":\"02\",\"message\":\"Current transaction does not exist or is in an invalid state.\",\"approved\":false,\"charge_transaction_id\":\"\",\"amount\":100,\"recurring\":false,\"completed\":\"2023-07-14T21:33:54.2518371Z\"})
+ end
+
+ def successful_refund_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_w5A8LS3C1kqdtrCJxWeRqQ\",\"amount\":10000,\"completed\":\"2023-07-15T01:01:58.3190631+00:00\"})
+ end
+
+ def failed_refund_response
+ %({\"type\":\"https://httpstatuses.com/400\",\"title\":\"Invalid Request\",\"status\":400,\"detail\":\"Specified transaction does not exist.\",\"traceId\":\"00-e9b47344b951b400c34ce541a22e96a7-5ece5267ae02ef3d-00\"})
+ end
+
+ def successful_void_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_w5A8LS3C1kqdtrCJxWeRqQ\",\"amount\":10000,\"completed\":\"2023-07-15T01:01:58.3190631+00:00\"})
+ end
+
+ def failed_void_response
+ %({\"type\":\"https://httpstatuses.com/400\",\"title\":\"Invalid Request\",\"status\":400,\"detail\":\"Specified transaction does not exist.\",\"traceId\":\"00-e9b47344b951b400c34ce541a22e96a7-5ece5267ae02ef3d-00\"})
+ end
+
+ def successful_token_response
+ %({\"id\":\"tok_Ub1AHj7x1U6cUF8x8KDKAw\",\"type\":\"credit_card\",\"customer_id\":null,\"brand\":\"Visa\",\"bin\":\"411111\",\"last_four\":\"1111\",\"expiration\":\"0129\",\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"is_default\":false})
+ end
+
+ def failed_token_response
+ %({\"id\":\"Ji-YEeijmUmiFB6mz_iIUA\",\"response_code\":\"400\",\"message\":\"InvalidRequestException: Card number is invalid.\",\"approved\":false,\"completed\":\"2023-07-15T01:10:47.9188024Z\",\"success\":false})
+ end
+end
From d8af5bf2d83cb65a293f2d6a68bab68e017f8e3d Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 4 Aug 2023 15:05:37 -0400
Subject: [PATCH 136/390] Braintree: Additional tests for credit transactions
---
CHANGELOG | 1 +
.../gateways/remote_braintree_blue_test.rb | 19 +++++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index c48e241e172..1bc8f15d3bf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@
* CyberSource: Add merchant_id [almalee24] #4844
* Global Collect: Add agent numeric code and house number field [yunnydang] #4847
* Deepstack: Add Deepstack Gateway [khoinguyendeepstack] #4830
+* Braintree: Additional tests for credit transactions [jcreiff] #4848
== Version 1.134.0 (July 25, 2023)
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 3cc5b8cb5c9..9c69d45ffb7 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -927,6 +927,25 @@ def test_successful_credit_with_merchant_account_id
assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
end
+ def test_failed_credit_with_merchant_account_id
+ assert response = @gateway.credit(@declined_amount, credit_card('4000111111111115'), merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id])
+ assert_failure response
+ assert_equal '2000 Do Not Honor', response.message
+ assert_equal '2000 : Do Not Honor', response.params['braintree_transaction']['additional_processor_response']
+ end
+
+ def test_successful_credit_using_card_token
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ assert_equal 'OK', response.message
+ credit_card_token = response.params['credit_card_token']
+
+ assert response = @gateway.credit(@amount, credit_card_token, { merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id], payment_method_token: true })
+ assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml AND get credits enabled in your Sandbox account for this to pass.'
+ assert_equal '1002 Processed', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
def test_successful_authorize_with_merchant_account_id
assert response = @gateway.authorize(@amount, @credit_card, merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id])
assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.'
From a0538f5e19e6765ebd1dc79dfc6d0cc2b995376f Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Mon, 7 Aug 2023 14:53:23 -0400
Subject: [PATCH 137/390] Rapyd: Change nesting of description,
statement_descriptor, complete_payment_url, and error_payment_url
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 32 +++++++++------
test/remote/gateways/remote_rapyd_test.rb | 5 ++-
test/unit/gateways/rapyd_test.rb | 39 +++++++++++++++++++
4 files changed, 63 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1bc8f15d3bf..3a628377910 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,7 @@
* Global Collect: Add agent numeric code and house number field [yunnydang] #4847
* Deepstack: Add Deepstack Gateway [khoinguyendeepstack] #4830
* Braintree: Additional tests for credit transactions [jcreiff] #4848
+* Rapyd: Change nesting of description, statement_descriptor, complete_payment_url, and error_payment_url [jcreiff] #4849
== Version 1.134.0 (July 25, 2023)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 9761ed7f642..dbc2e6ae238 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -67,7 +67,7 @@ def store(payment, options = {})
add_metadata(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
- add_payment_urls(post, options)
+ add_payment_urls(post, options, 'store')
add_address(post, payment, options)
commit(:post, 'customers', post)
end
@@ -135,7 +135,7 @@ def add_payment(post, payment, options)
elsif payment.is_a?(Check)
add_ach(post, payment, options)
else
- add_token(post, payment, options)
+ add_tokens(post, payment, options)
end
end
@@ -174,10 +174,13 @@ def add_ach(post, payment, options)
post[:payment_method][:fields][:payment_purpose] = options[:payment_purpose] if options[:payment_purpose]
end
- def add_token(post, payment, options)
- return unless token = payment.split('|')[1]
+ def add_tokens(post, payment, options)
+ return unless payment.respond_to?(:split)
+
+ customer_id, card_id = payment.split('|')
- post[:payment_method] = token
+ post[:customer] = customer_id
+ post[:payment_method] = card_id
end
def add_3ds(post, payment, options)
@@ -201,20 +204,25 @@ def add_ewallet(post, options)
end
def add_payment_fields(post, options)
- post[:payment] = {}
-
- post[:payment][:description] = options[:description] if options[:description]
- post[:payment][:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
+ post[:description] = options[:description] if options[:description]
+ post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
end
- def add_payment_urls(post, options)
- post[:complete_payment_url] = options[:complete_payment_url] if options[:complete_payment_url]
- post[:error_payment_url] = options[:error_payment_url] if options[:error_payment_url]
+ def add_payment_urls(post, options, action = '')
+ if action == 'store'
+ url_location = post[:payment_method]
+ else
+ url_location = post
+ end
+
+ url_location[:complete_payment_url] = options[:complete_payment_url] if options[:complete_payment_url]
+ url_location[:error_payment_url] = options[:error_payment_url] if options[:error_payment_url]
end
def add_customer_data(post, payment, options, action = '')
post[:phone_number] = options.dig(:billing_address, :phone) .gsub(/\D/, '') unless options[:billing_address].nil?
post[:email] = options[:email]
+ return if payment.is_a?(String)
return add_customer_id(post, options) if options[:customer_id]
if action == 'store'
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 30009b817a4..ac4517c81cc 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -225,8 +225,9 @@ def test_successful_store_and_purchase
assert store.params.dig('data', 'default_payment_method')
# 3DS authorization is required on storing a payment method for future transactions
- # purchase = @gateway.purchase(100, store.authorization, @options.merge(customer_id: customer_id))
- # assert_sucess purchase
+ # This test verifies that the card id and customer id are sent with the purchase
+ purchase = @gateway.purchase(100, store.authorization, @options)
+ assert_match(/The request tried to use a card ID, but the cardholder has not completed the 3DS verification process./, purchase.message)
end
def test_successful_store_and_unstore
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 9fc082ee9b5..69e6f922c78 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -242,6 +242,45 @@ def test_successful_store_with_customer_object
assert_success response
end
+ def test_payment_urls_correctly_nested_by_operation
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request_body = JSON.parse(data)
+ assert_equal @options[:complete_payment_url], request_body['payment_method']['complete_payment_url']
+ assert_equal @options[:error_payment_url], request_body['payment_method']['error_payment_url']
+ end.respond_with(successful_store_response)
+
+ assert_success response
+
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request_body = JSON.parse(data)
+ assert_equal @options[:complete_payment_url], request_body['complete_payment_url']
+ assert_equal @options[:error_payment_url], request_body['error_payment_url']
+ end.respond_with(successful_store_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_customer_and_card_id
+ store = stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card, @options)
+ end.respond_with(successful_store_response)
+
+ assert customer_id = store.params.dig('data', 'id')
+ assert card_id = store.params.dig('data', 'default_payment_method')
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, store.authorization, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request_body = JSON.parse(data)
+ assert_equal request_body['customer'], customer_id
+ assert_equal request_body['payment_method'], card_id
+ end.respond_with(successful_purchase_response)
+ end
+
def test_three_d_secure
options = {
three_d_secure: {
From d39ccdd8a7cc6284255f310a1ae9811ef2ce1f98 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Tue, 15 Aug 2023 16:30:41 -0400
Subject: [PATCH 138/390] Rapyd: Add merchant_reference_id
This maps order_id to the merchant_reference_id field at Rapyd
CER-831
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 1 +
test/remote/gateways/remote_rapyd_test.rb | 3 ++-
test/unit/gateways/rapyd_test.rb | 18 +++++++++++++++++-
4 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3a628377910..9398904fb1a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,7 @@
* Deepstack: Add Deepstack Gateway [khoinguyendeepstack] #4830
* Braintree: Additional tests for credit transactions [jcreiff] #4848
* Rapyd: Change nesting of description, statement_descriptor, complete_payment_url, and error_payment_url [jcreiff] #4849
+* Rapyd: Add merchant_reference_id [jcreiff] #4858
== Version 1.134.0 (July 25, 2023)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index dbc2e6ae238..ad7bdbdfb6d 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -127,6 +127,7 @@ def add_address(post, creditcard, options)
def add_invoice(post, money, options)
post[:amount] = money.zero? ? 0 : amount(money).to_f.to_s
post[:currency] = (options[:currency] || currency(money))
+ post[:merchant_reference_id] = options[:merchant_reference_id] || options[:order_id]
end
def add_payment(post, payment, options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index ac4517c81cc..52d3869da48 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -16,7 +16,8 @@ def setup
description: 'Describe this transaction',
statement_descriptor: 'Statement Descriptor',
email: 'test@example.com',
- billing_address: address(name: 'Jim Reynolds')
+ billing_address: address(name: 'Jim Reynolds'),
+ order_id: '987654321'
}
@ach_options = {
pm_type: 'us_ach_bank',
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 69e6f922c78..2349962d451 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -18,7 +18,8 @@ def setup
description: 'Describe this transaction',
statement_descriptor: 'Statement Descriptor',
email: 'test@example.com',
- billing_address: address(name: 'Jim Reynolds')
+ billing_address: address(name: 'Jim Reynolds'),
+ order_id: '987654321'
}
@metadata = {
@@ -87,6 +88,21 @@ def test_successful_purchase_with_payment_options
assert_match(/"error_payment_url":"www.google.com"/, data)
assert_match(/"description":"Describe this transaction"/, data)
assert_match(/"statement_descriptor":"Statement Descriptor"/, data)
+ assert_match(/"merchant_reference_id":"987654321"/, data)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_explicit_merchant_reference_id
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_reference_id: '99988877776' }))
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"complete_payment_url":"www.google.com"/, data)
+ assert_match(/"error_payment_url":"www.google.com"/, data)
+ assert_match(/"description":"Describe this transaction"/, data)
+ assert_match(/"statement_descriptor":"Statement Descriptor"/, data)
+ assert_match(/"merchant_reference_id":"99988877776"/, data)
end.respond_with(successful_authorize_response)
assert_success response
From 86679d39087830d49913cf04ef7614741ba21ccd Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Wed, 16 Aug 2023 15:36:21 -0400
Subject: [PATCH 139/390] Braintree: Add check for ACH on credit
Return DIRECT_BANK_ERROR message if credit is attempted using a check
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/braintree_blue.rb | 2 ++
test/unit/gateways/braintree_blue_test.rb | 8 ++++++++
3 files changed, 11 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 9398904fb1a..94ba474f079 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,7 @@
* Braintree: Additional tests for credit transactions [jcreiff] #4848
* Rapyd: Change nesting of description, statement_descriptor, complete_payment_url, and error_payment_url [jcreiff] #4849
* Rapyd: Add merchant_reference_id [jcreiff] #4858
+* Braintree: Return error for ACH on credit [jcreiff] #4859
== Version 1.134.0 (July 25, 2023)
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 0bc075e35af..0d5468cdfc7 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -106,6 +106,8 @@ def purchase(money, credit_card_or_vault_id, options = {})
end
def credit(money, credit_card_or_vault_id, options = {})
+ return Response.new(false, DIRECT_BANK_ERROR) if credit_card_or_vault_id.is_a? Check
+
create_transaction(:credit, money, credit_card_or_vault_id, options)
end
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index da16bc25950..ab95429829e 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -1368,6 +1368,14 @@ def test_returns_error_on_authorize_when_passing_a_bank_account
assert_equal 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.', response.message
end
+ def test_returns_error_on_general_credit_when_passing_a_bank_account
+ bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
+ response = @gateway.credit(100, bank_account, {})
+
+ assert_failure response
+ assert_equal 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.', response.message
+ end
+
def test_error_on_store_bank_account_without_a_mandate
options = {
billing_address: {
From b7e419348921a7d6b34f19e9d05e5767d303ad3d Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 18 Aug 2023 09:49:54 -0400
Subject: [PATCH 140/390] Rapyd: Update handling of `ewallet` and billing
address phone
Changes `ewallet_id` to `ewallet` to match Rapyd's specifications
Adds handling for alternate method of passing `phone_number` in the `billing_address` object
CER-843, CER-844
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 8 +++++---
test/remote/gateways/remote_rapyd_test.rb | 14 +++++++++++++-
test/unit/gateways/rapyd_test.rb | 16 ++++++++++++++++
4 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 94ba474f079..17de308006f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@
* Rapyd: Change nesting of description, statement_descriptor, complete_payment_url, and error_payment_url [jcreiff] #4849
* Rapyd: Add merchant_reference_id [jcreiff] #4858
* Braintree: Return error for ACH on credit [jcreiff] #4859
+* Rapyd: Update handling of ewallet and billing address phone [jcreiff] #4863
== Version 1.134.0 (July 25, 2023)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index ad7bdbdfb6d..637f7250af4 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -201,7 +201,7 @@ def add_metadata(post, options)
end
def add_ewallet(post, options)
- post[:ewallet_id] = options[:ewallet_id] if options[:ewallet_id]
+ post[:ewallet] = options[:ewallet_id] if options[:ewallet_id]
end
def add_payment_fields(post, options)
@@ -221,7 +221,8 @@ def add_payment_urls(post, options, action = '')
end
def add_customer_data(post, payment, options, action = '')
- post[:phone_number] = options.dig(:billing_address, :phone) .gsub(/\D/, '') unless options[:billing_address].nil?
+ phone_number = options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ post[:phone_number] = phone_number.gsub(/\D/, '') unless phone_number.nil?
post[:email] = options[:email]
return if payment.is_a?(String)
return add_customer_id(post, options) if options[:customer_id]
@@ -236,9 +237,10 @@ def add_customer_data(post, payment, options, action = '')
def customer_fields(payment, options)
return if options[:customer_id]
+ customer_address = address(options)
customer_data = {}
customer_data[:name] = "#{payment.first_name} #{payment.last_name}" unless payment.is_a?(String)
- customer_data[:addresses] = [address(options)]
+ customer_data[:addresses] = [customer_address] if customer_address
customer_data
end
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 52d3869da48..e6dee2d82c7 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -100,6 +100,18 @@ def test_successful_purchase_with_address
assert_equal 'SUCCESS', response.message
end
+ def test_successful_purchase_with_no_address
+ credit_card = credit_card('4111111111111111', month: '12', year: '2035', verification_value: '345')
+
+ options = @options.dup
+ options[:billing_address] = nil
+ options[:pm_type] = 'gb_mastercard_card'
+
+ response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_successful_purchase_using_ach
response = @gateway.purchase(100000, @check, @ach_options)
assert_success response
@@ -108,7 +120,7 @@ def test_successful_purchase_using_ach
end
def test_successful_purchase_with_options
- options = @options.merge(metadata: @metadata, ewallet_id: 'ewallet_1a867a32b47158b30a8c17d42f12f3f1')
+ options = @options.merge(metadata: @metadata, ewallet_id: 'ewallet_897aca846f002686e14677541f78a0f4')
response = @gateway.purchase(100000, @credit_card, options)
assert_success response
assert_equal 'SUCCESS', response.message
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 2349962d451..fd3cd6b3ba2 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -246,6 +246,22 @@ def test_successful_purchase_with_customer_object
end
end
+ def test_successful_purchase_with_billing_address_phone_variations
+ stub_comms(@gateway, :ssl_request) do
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ @gateway.purchase(@amount, @credit_card, { billing_address: { phone_number: '919.123.1234' } })
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ assert_match(/"phone_number":"9191231234"/, data)
+ end
+
+ stub_comms(@gateway, :ssl_request) do
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ @gateway.purchase(@amount, @credit_card, { billing_address: { phone: '919.123.1234' } })
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ assert_match(/"phone_number":"9191231234"/, data)
+ end
+ end
+
def test_successful_store_with_customer_object
response = stub_comms(@gateway, :ssl_request) do
@gateway.store(@credit_card, @options)
From 1b08fbdc0c88a7a178ab881aed20e5e3b559ab7a Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 14 Aug 2023 15:52:27 -0500
Subject: [PATCH 141/390] IPG: Refactor Credentials
Refactor IPG credentials to accept a combined user ID/store ID string since this is what is provided to IPG merchants as their user ID.
ECS-2839
Unit: 31 tests, 135 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications, 100% passed
Remote: 18 tests, 42 assertions, 6 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications, 66.6667% passed
Closes #4854
---
CHANGELOG | 2 +-
lib/active_merchant/billing/gateways/ipg.rb | 9 ++++--
test/unit/gateways/ipg_test.rb | 32 +++++++++++++++++++++
3 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 17de308006f..49f90f41b8b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,7 +17,7 @@
* Rapyd: Add merchant_reference_id [jcreiff] #4858
* Braintree: Return error for ACH on credit [jcreiff] #4859
* Rapyd: Update handling of ewallet and billing address phone [jcreiff] #4863
-
+* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/ipg.rb b/lib/active_merchant/billing/gateways/ipg.rb
index 139bfdec1c0..8141d414e89 100644
--- a/lib/active_merchant/billing/gateways/ipg.rb
+++ b/lib/active_merchant/billing/gateways/ipg.rb
@@ -18,8 +18,8 @@ class IpgGateway < Gateway
ACTION_REQUEST_ITEMS = %w(vault unstore)
def initialize(options = {})
- requires!(options, :store_id, :user_id, :password, :pem, :pem_password)
- @credentials = options
+ requires!(options, :user_id, :password, :pem, :pem_password)
+ @credentials = options.merge(store_and_user_id_from(options[:user_id]))
@hosted_data_id = nil
super
end
@@ -395,6 +395,11 @@ def parse_element(reply, node)
return reply
end
+ def store_and_user_id_from(user_id)
+ split_credentials = user_id.split('._.')
+ { store_id: split_credentials[0].sub(/^WS/, ''), user_id: split_credentials[1] }
+ end
+
def message_from(response)
[response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
end
diff --git a/test/unit/gateways/ipg_test.rb b/test/unit/gateways/ipg_test.rb
index 867f2e9878a..255fbea4dce 100644
--- a/test/unit/gateways/ipg_test.rb
+++ b/test/unit/gateways/ipg_test.rb
@@ -332,6 +332,38 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_store_and_user_id_from_with_complete_credentials
+ test_combined_user_id = 'WS5921102002._.1'
+ split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id)
+
+ assert_equal '5921102002', split_credentials[:store_id]
+ assert_equal '1', split_credentials[:user_id]
+ end
+
+ def test_store_and_user_id_from_missing_store_id_prefix
+ test_combined_user_id = '5921102002._.1'
+ split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id)
+
+ assert_equal '5921102002', split_credentials[:store_id]
+ assert_equal '1', split_credentials[:user_id]
+ end
+
+ def test_store_and_user_id_misplaced_store_id_prefix
+ test_combined_user_id = '5921102002WS._.1'
+ split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id)
+
+ assert_equal '5921102002WS', split_credentials[:store_id]
+ assert_equal '1', split_credentials[:user_id]
+ end
+
+ def test_store_and_user_id_from_missing_delimiter
+ test_combined_user_id = 'WS59211020021'
+ split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id)
+
+ assert_equal '59211020021', split_credentials[:store_id]
+ assert_equal nil, split_credentials[:user_id]
+ end
+
def test_message_from_just_with_transaction_result
am_response = { TransactionResult: 'success !' }
assert_equal 'success !', @gateway.send(:message_from, am_response)
From 8cd93c24c075cf89dd57305df6bfbe65076329ef Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Fri, 18 Aug 2023 11:28:19 -0700
Subject: [PATCH 142/390] Braintree Blue: Update the credit card details
transaction hash
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/braintree_blue.rb | 5 ++++-
test/remote/gateways/remote_braintree_blue_test.rb | 9 +++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 49f90f41b8b..8b12d7f8ff5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,7 @@
* Braintree: Return error for ACH on credit [jcreiff] #4859
* Rapyd: Update handling of ewallet and billing address phone [jcreiff] #4863
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
+* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 0d5468cdfc7..1da4cacb229 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -601,7 +601,10 @@ def transaction_hash(result)
'bin' => transaction.credit_card_details.bin,
'last_4' => transaction.credit_card_details.last_4,
'card_type' => transaction.credit_card_details.card_type,
- 'token' => transaction.credit_card_details.token
+ 'token' => transaction.credit_card_details.token,
+ 'debit' => transaction.credit_card_details.debit,
+ 'prepaid' => transaction.credit_card_details.prepaid,
+ 'issuing_bank' => transaction.credit_card_details.issuing_bank
}
if transaction.risk_data
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 9c69d45ffb7..ea68f247188 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -1226,6 +1226,15 @@ def test_successful_purchase_with_processor_authorization_code
assert_not_nil response.params['braintree_transaction']['processor_authorization_code']
end
+ def test_successful_purchase_with_with_prepaid_debit_issuing_bank
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['issuing_bank']
+ end
+
def test_unsucessful_purchase_using_a_bank_account_token_not_verified
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
response = @gateway.store(bank_account, @options.merge(@check_required_options))
From 14c6225ed0c4c37a57ba5ff9db456f5ef55f119d Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 14 Aug 2023 16:28:44 -0500
Subject: [PATCH 143/390] VisaNet Peru: Update generate_purchase_number_stamp
Update generate_purchase_number_stamp to always produce unique 12-digit
alphanumberic value even if multiple transactions occur at the same time.
Unit:
16 tests, 100 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
Gemfile | 1 +
.../billing/gateways/visanet_peru.rb | 2 +-
test/unit/gateways/visanet_peru_test.rb | 53 ++++++++++++-------
4 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8b12d7f8ff5..bbbc58ec63e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,7 @@
* Rapyd: Update handling of ewallet and billing address phone [jcreiff] #4863
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
+* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/Gemfile b/Gemfile
index f653ef90ec3..f24e5c86218 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,4 +10,5 @@ group :test, :remote_test do
gem 'jose', '~> 1.1.3'
gem 'jwe'
gem 'mechanize'
+ gem 'timecop'
end
diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb
index 25889cccd00..6bd87e406c1 100644
--- a/lib/active_merchant/billing/gateways/visanet_peru.rb
+++ b/lib/active_merchant/billing/gateways/visanet_peru.rb
@@ -143,7 +143,7 @@ def split_authorization(authorization)
end
def generate_purchase_number_stamp
- Time.now.to_f.to_s.delete('.')[1..10] + rand(99).to_s
+ rand(('9' * 12).to_i).to_s.center(12, rand(9).to_s)
end
def commit(action, params, options = {})
diff --git a/test/unit/gateways/visanet_peru_test.rb b/test/unit/gateways/visanet_peru_test.rb
index 81186a72ad9..896f2bbb5a9 100644
--- a/test/unit/gateways/visanet_peru_test.rb
+++ b/test/unit/gateways/visanet_peru_test.rb
@@ -1,4 +1,5 @@
require 'test_helper'
+require 'timecop'
class VisanetPeruTest < Test::Unit::TestCase
include CommStub
@@ -40,26 +41,38 @@ def test_failed_purchase
end
def test_nonconsecutive_purchase_numbers
- pn1, pn2 = nil
-
- response1 = stub_comms(@gateway, :ssl_request) do
- @gateway.authorize(@amount, @credit_card, @options)
- end.check_request do |_method, _endpoint, data, _headers|
- pn1 = JSON.parse(data)['purchaseNumber']
- end.respond_with(successful_authorize_response)
-
- # unit test is unrealistically speedy relative to real-world performance
- sleep 0.1
-
- response2 = stub_comms(@gateway, :ssl_request) do
- @gateway.authorize(@amount, @credit_card, @options)
- end.check_request do |_method, _endpoint, data, _headers|
- pn2 = JSON.parse(data)['purchaseNumber']
- end.respond_with(successful_authorize_response)
-
- assert_success response1
- assert_success response2
- assert_not_equal(pn1, pn2)
+ purchase_times = []
+
+ Timecop.freeze do
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ purchase_times << JSON.parse(data)['purchaseNumber'].to_i
+ end.respond_with(successful_authorize_response)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ purchase_times << JSON.parse(data)['purchaseNumber'].to_i
+ end.respond_with(successful_authorize_response)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ purchase_times << JSON.parse(data)['purchaseNumber'].to_i
+ end.respond_with(successful_authorize_response)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ purchase_times << JSON.parse(data)['purchaseNumber'].to_i
+ end.respond_with(successful_authorize_response)
+ end
+
+ purchase_times.each do |t|
+ assert_equal(t.to_s.length, 12)
+ end
+ assert_equal(purchase_times.uniq.size, purchase_times.size)
end
def test_successful_authorize
From 00fd53ff9b9fb59ec5e0ad244947e93bcfa0ba4a Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 11 Aug 2023 09:30:37 -0500
Subject: [PATCH 144/390] Adding Oauth Response for access tokens
Add new OAuth Response error handling to PayTrace,
Quickbooks, Simetrik, Alelo, CheckoutV2 and Airwallex.
---
CHANGELOG | 1 +
.../billing/gateways/airwallex.rb | 12 ++++++---
lib/active_merchant/billing/gateways/alelo.rb | 26 ++++++++++++++++---
.../billing/gateways/checkout_v2.rb | 19 +++++++++++---
.../billing/gateways/pay_trace.rb | 21 ++++++++++-----
.../billing/gateways/quickbooks.rb | 19 ++++++++++----
.../billing/gateways/simetrik.rb | 18 ++++++++-----
lib/active_merchant/errors.rb | 6 ++++-
test/remote/gateways/remote_airwallex_test.rb | 7 +++++
test/remote/gateways/remote_alelo_test.rb | 10 ++++---
.../gateways/remote_checkout_v2_test.rb | 16 ++++++++++++
test/remote/gateways/remote_pay_trace_test.rb | 17 ++++++++++++
.../remote/gateways/remote_quickbooks_test.rb | 17 ++++++++++++
test/remote/gateways/remote_simetrik_test.rb | 16 ++++++++++++
test/unit/gateways/airwallex_test.rb | 21 +++++++--------
test/unit/gateways/alelo_test.rb | 9 +++++++
test/unit/gateways/checkout_v2_test.rb | 22 ++++++++--------
test/unit/gateways/pay_trace_test.rb | 25 ++++++++++--------
test/unit/gateways/quickbooks_test.rb | 9 +++++++
test/unit/gateways/simetrik_test.rb | 22 ++++++++--------
20 files changed, 236 insertions(+), 77 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index bbbc58ec63e..e47c57f8496 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,7 @@
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
+* Adding Oauth Response for access tokens [almalee24] #4851
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index fd1e06f427d..d9faced5ac2 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = setup_access_token
+ @access_token = options[:access_token] || setup_access_token
end
def purchase(money, card, options = {})
@@ -133,8 +133,14 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- response = ssl_post(build_request_url(:login), nil, token_headers)
- JSON.parse(response)['token']
+ raw_response = ssl_post(build_request_url(:login), nil, token_headers)
+ response = JSON.parse(raw_response)
+ if (token = response['token'])
+ token
+ else
+ oauth_response = Response.new(false, response['message'])
+ raise OAuthResponseError.new(oauth_response)
+ end
end
def build_request_url(action, id = nil)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index fd1cfc11a5f..381b5859372 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,8 +110,18 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
- Response.new(true, parsed[:access_token], parsed)
+ begin
+ raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ if (access_token = response[:access_token])
+ Response.new(true, access_token, response)
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def remote_encryption_key(access_token)
@@ -144,9 +154,11 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
+ raise e unless retry?(try_again, e, :access_token)
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -206,9 +218,11 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
+ if retry?(try_again, e, :encryption_key)
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -217,6 +231,10 @@ def commit(action, body, options, try_again = true)
end
end
+ def retry?(try_again, error, key)
+ try_again && %w(401 404).include?(error.response.code) && @options[key].present?
+ end
+
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 13f6f7e757f..4634966eda6 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -18,13 +18,13 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
- @access_token = nil
+ @access_token = options[:access_token] || nil
if options.has_key?(:secret_key)
requires!(options, :secret_key)
else
requires!(options, :client_id, :client_secret)
- @access_token = setup_access_token
+ @access_token ||= setup_access_token
end
super
@@ -341,8 +341,19 @@ def access_token_url
def setup_access_token
request = 'grant_type=client_credentials'
- response = parse(ssl_post(access_token_url, request, access_token_header))
- response['access_token']
+ begin
+ raw_response = ssl_post(access_token_url, request, access_token_header)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+
+ if (access_token = response['access_token'])
+ access_token
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def commit(action, post, options, authorization = nil, method = :post)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 53203d51f96..8c338687df1 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token
+ acquire_access_token unless options[:access_token]
end
def purchase(money, payment_or_customer_id, options = {})
@@ -187,10 +187,15 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = JSON.parse(response)
+ json_response = parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
+ if json_response.include?('error')
+ oauth_response = Response.new(false, json_response['error_description'])
+ raise OAuthResponseError.new(oauth_response)
+ else
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
+ end
end
private
@@ -373,6 +378,12 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
+ handle_final_response(action, response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -385,8 +396,6 @@ def commit(action, parameters)
test: test?,
error_code: success ? nil : error_code_from(response)
)
- rescue JSON::ParserError
- unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6d9f14f3445..6197581bbe7 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,12 +305,17 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- response = ssl_post(REFRESH_URI, data, headers)
- json_response = JSON.parse(response)
+ begin
+ response = ssl_post(REFRESH_URI, data, headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
+ end
end
def cvv_code_from(response)
@@ -358,6 +363,10 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
+
+ error_code = JSON.parse(response_error.response.body)['code']
+ raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
+
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index 5c436acab95..f3b0863eef8 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = {}
+ @access_token = options[:access_token] || {}
sign_access_token()
end
@@ -356,12 +356,18 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
- response = parse(ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- }))
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ begin
+ raw_response = ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ })
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ end
end
end
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index b017c45114a..882095d0358 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,7 +23,11 @@ def initialize(response, message = nil)
end
def to_s
- "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
+ if response&.message&.start_with?('Failed with')
+ response.message
+ else
+ "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
+ end
end
end
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 5cbc4053c7d..24aa9fe3b61 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,6 +14,13 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
+ gateway.send :setup_access_token
+ end
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index be4d9ae9059..8a4fef24e7b 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::ResponseError) do
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,9 +145,11 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match %r{invalid_client}, response.message
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_match(/401/, error.message)
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index e8b1d0c1e90..9ca1b89629d 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -118,6 +118,22 @@ def setup
)
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.send :setup_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 400 Bad Request'
+ end
+
def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index b10e5119e3a..6c56f840353 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -39,6 +39,23 @@ def test_acquire_token
assert_not_nil response['access_token']
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ gateway.send :acquire_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(1000, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ end
+
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index f3457706af5..6b295967b22 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,6 +18,23 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+ gateway.send :refresh_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index b1e2eb24daf..90f66e2f844 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,6 +78,22 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.send :fetch_access_token
+ end
+ end
+
+ def test_failed_authorize_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.authorize(@amount, @credit_card, @authorize_options_success)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index ade541ce88e..9d707bcba1c 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class AirwallexGateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -28,6 +18,15 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_setup_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
+ end
+
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 3e6c9f0c1c9..86e35e917f3 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,6 +19,15 @@ def setup
}
end
+ def test_fetch_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway .send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 7256db54e3f..8b74716e1be 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CheckoutV2Gateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -17,7 +7,7 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
@gateway_api = CheckoutV2Gateway.new({
secret_key: '1111111111111',
public_key: '2222222222222'
@@ -27,6 +17,15 @@ def setup
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with 400 Bad Request/, error.message)
+ end
+
def test_successful_purchase
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
@@ -259,6 +258,7 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
def test_successful_render_for_oauth
processing_channel_id = 'abcd123'
+
response = stub_comms(@gateway_oauth, :ssl_request) do
@gateway_oauth.purchase(@amount, @credit_card, { processing_channel_id: processing_channel_id })
end.check_request do |_method, _endpoint, data, headers|
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 09f13807e83..42be462bbf0 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class PayTraceGateway < Gateway
- def acquire_access_token
- @options[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -24,6 +14,19 @@ def setup
}
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ access_token_response = {
+ error: 'invalid_grant',
+ error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ }.to_json
+ @gateway.expects(:ssl_post).returns(access_token_response)
+ @gateway.send(:acquire_access_token)
+ end
+
+ assert_match(/Failed with The provided authorization grant is invalid/, error.message)
+ end
+
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 7e48cce44ef..9b7a6f94a5b 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,6 +32,15 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
+ def test_refresh_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @oauth_2_gateway .send(:refresh_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb
index c120b2e99fe..f47a31203a9 100644
--- a/test/unit/gateways/simetrik_test.rb
+++ b/test/unit/gateways/simetrik_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SimetrikGateway < Gateway
- def fetch_access_token
- @access_token[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class SimetrikTest < Test::Unit::TestCase
def setup
@token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205'
@@ -17,7 +7,8 @@ def setup
@gateway = SimetrikGateway.new(
client_id: 'client_id',
client_secret: 'client_secret_key',
- audience: 'audience_url'
+ audience: 'audience_url',
+ access_token: { expires_at: Time.new.to_i }
)
@credit_card = CreditCard.new(
first_name: 'sergiod',
@@ -170,6 +161,15 @@ def test_success_purchase_with_billing_address
assert response.test?
end
+ def test_fetch_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway.send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_success_purchase_with_shipping_address
expected_body = JSON.parse(@authorize_capture_expected_body.dup)
expected_body['forward_payload']['order']['shipping_address'] = address
From 717232501a796d4defd5e352b83098a834351072 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 22 Aug 2023 09:57:01 -0500
Subject: [PATCH 145/390] Revert "Adding Oauth Response for access tokens"
This reverts commit 00fd53ff9b9fb59ec5e0ad244947e93bcfa0ba4a.
---
CHANGELOG | 1 -
.../billing/gateways/airwallex.rb | 12 +++------
lib/active_merchant/billing/gateways/alelo.rb | 26 +++----------------
.../billing/gateways/checkout_v2.rb | 19 +++-----------
.../billing/gateways/pay_trace.rb | 21 +++++----------
.../billing/gateways/quickbooks.rb | 19 ++++----------
.../billing/gateways/simetrik.rb | 18 +++++--------
lib/active_merchant/errors.rb | 6 +----
test/remote/gateways/remote_airwallex_test.rb | 7 -----
test/remote/gateways/remote_alelo_test.rb | 10 +++----
.../gateways/remote_checkout_v2_test.rb | 16 ------------
test/remote/gateways/remote_pay_trace_test.rb | 17 ------------
.../remote/gateways/remote_quickbooks_test.rb | 17 ------------
test/remote/gateways/remote_simetrik_test.rb | 16 ------------
test/unit/gateways/airwallex_test.rb | 21 ++++++++-------
test/unit/gateways/alelo_test.rb | 9 -------
test/unit/gateways/checkout_v2_test.rb | 22 ++++++++--------
test/unit/gateways/pay_trace_test.rb | 25 ++++++++----------
test/unit/gateways/quickbooks_test.rb | 9 -------
test/unit/gateways/simetrik_test.rb | 22 ++++++++--------
20 files changed, 77 insertions(+), 236 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index e47c57f8496..bbbc58ec63e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,7 +20,6 @@
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
-* Adding Oauth Response for access tokens [almalee24] #4851
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index d9faced5ac2..fd1e06f427d 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = options[:access_token] || setup_access_token
+ @access_token = setup_access_token
end
def purchase(money, card, options = {})
@@ -133,14 +133,8 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- raw_response = ssl_post(build_request_url(:login), nil, token_headers)
- response = JSON.parse(raw_response)
- if (token = response['token'])
- token
- else
- oauth_response = Response.new(false, response['message'])
- raise OAuthResponseError.new(oauth_response)
- end
+ response = ssl_post(build_request_url(:login), nil, token_headers)
+ JSON.parse(response)['token']
end
def build_request_url(action, id = nil)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index 381b5859372..fd1cfc11a5f 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,18 +110,8 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- begin
- raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
- if (access_token = response[:access_token])
- Response.new(true, access_token, response)
- else
- raise OAuthResponseError.new(response)
- end
- end
+ parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
+ Response.new(true, parsed[:access_token], parsed)
end
def remote_encryption_key(access_token)
@@ -154,11 +144,9 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
- rescue ActiveMerchant::OAuthResponseError => e
- raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise e unless retry?(try_again, e, :access_token)
+ raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -218,11 +206,9 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
- rescue ActiveMerchant::OAuthResponseError => e
- raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if retry?(try_again, e, :encryption_key)
+ if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -231,10 +217,6 @@ def commit(action, body, options, try_again = true)
end
end
- def retry?(try_again, error, key)
- try_again && %w(401 404).include?(error.response.code) && @options[key].present?
- end
-
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 4634966eda6..13f6f7e757f 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -18,13 +18,13 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
- @access_token = options[:access_token] || nil
+ @access_token = nil
if options.has_key?(:secret_key)
requires!(options, :secret_key)
else
requires!(options, :client_id, :client_secret)
- @access_token ||= setup_access_token
+ @access_token = setup_access_token
end
super
@@ -341,19 +341,8 @@ def access_token_url
def setup_access_token
request = 'grant_type=client_credentials'
- begin
- raw_response = ssl_post(access_token_url, request, access_token_header)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
-
- if (access_token = response['access_token'])
- access_token
- else
- raise OAuthResponseError.new(response)
- end
- end
+ response = parse(ssl_post(access_token_url, request, access_token_header))
+ response['access_token']
end
def commit(action, post, options, authorization = nil, method = :post)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 8c338687df1..53203d51f96 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token unless options[:access_token]
+ acquire_access_token
end
def purchase(money, payment_or_customer_id, options = {})
@@ -187,15 +187,10 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = parse(response)
+ json_response = JSON.parse(response)
- if json_response.include?('error')
- oauth_response = Response.new(false, json_response['error_description'])
- raise OAuthResponseError.new(oauth_response)
- else
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
- end
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
end
private
@@ -378,12 +373,6 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
- handle_final_response(action, response)
- rescue JSON::ParserError
- unparsable_response(raw_response)
- end
-
- def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -396,6 +385,8 @@ def handle_final_response(action, response)
test: test?,
error_code: success ? nil : error_code_from(response)
)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6197581bbe7..6d9f14f3445 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,17 +305,12 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- begin
- response = ssl_post(REFRESH_URI, data, headers)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- json_response = JSON.parse(response)
+ response = ssl_post(REFRESH_URI, data, headers)
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
- end
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
end
def cvv_code_from(response)
@@ -363,10 +358,6 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
-
- error_code = JSON.parse(response_error.response.body)['code']
- raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
-
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index f3b0863eef8..5c436acab95 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = options[:access_token] || {}
+ @access_token = {}
sign_access_token()
end
@@ -356,18 +356,12 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
+ response = parse(ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ }))
- begin
- raw_response = ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- })
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
- end
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
end
end
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index 882095d0358..b017c45114a 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,11 +23,7 @@ def initialize(response, message = nil)
end
def to_s
- if response&.message&.start_with?('Failed with')
- response.message
- else
- "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
- end
+ "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 24aa9fe3b61..5cbc4053c7d 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,13 +14,6 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
- gateway.send :setup_access_token
- end
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index 8a4fef24e7b..be4d9ae9059 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ error = assert_raises(ActiveMerchant::ResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,11 +145,9 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_match(/401/, error.message)
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid_client}, response.message
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 9ca1b89629d..e8b1d0c1e90 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -118,22 +118,6 @@ def setup
)
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
- gateway.send :setup_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with 400 Bad Request'
- end
-
def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index 6c56f840353..b10e5119e3a 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -39,23 +39,6 @@ def test_acquire_token
assert_not_nil response['access_token']
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
- gateway.send :acquire_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
-
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(1000, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
- end
-
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index 6b295967b22..f3457706af5 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,23 +18,6 @@ def setup
}
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
- gateway.send :refresh_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
-
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with 401 Unauthorized'
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index 90f66e2f844..b1e2eb24daf 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,22 +78,6 @@ def setup
}
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
- gateway.send :fetch_access_token
- end
- end
-
- def test_failed_authorize_with_failed_access_token
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
- gateway.authorize(@amount, @credit_card, @authorize_options_success)
- end
-
- assert_equal error.message, 'Failed with 401 Unauthorized'
- end
-
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index 9d707bcba1c..ade541ce88e 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,10 +1,20 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class AirwallexGateway
+ def setup_access_token
+ '12345678'
+ end
+ end
+ end
+end
+
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -18,15 +28,6 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
- def test_setup_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
- @gateway.send(:setup_access_token)
- end
-
- assert_match(/Failed with Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
- end
-
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 86e35e917f3..3e6c9f0c1c9 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,15 +19,6 @@ def setup
}
end
- def test_fetch_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @gateway .send(:fetch_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 8b74716e1be..7256db54e3f 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,5 +1,15 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CheckoutV2Gateway
+ def setup_access_token
+ '12345678'
+ end
+ end
+ end
+end
+
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -7,7 +17,7 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
@gateway_api = CheckoutV2Gateway.new({
secret_key: '1111111111111',
public_key: '2222222222222'
@@ -17,15 +27,6 @@ def setup
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
- def test_setup_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
- @gateway.send(:setup_access_token)
- end
-
- assert_match(/Failed with 400 Bad Request/, error.message)
- end
-
def test_successful_purchase
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
@@ -258,7 +259,6 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
def test_successful_render_for_oauth
processing_channel_id = 'abcd123'
-
response = stub_comms(@gateway_oauth, :ssl_request) do
@gateway_oauth.purchase(@amount, @credit_card, { processing_channel_id: processing_channel_id })
end.check_request do |_method, _endpoint, data, headers|
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 42be462bbf0..09f13807e83 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,10 +1,20 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayTraceGateway < Gateway
+ def acquire_access_token
+ @options[:access_token] = SecureRandom.hex(16)
+ end
+ end
+ end
+end
+
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -14,19 +24,6 @@ def setup
}
end
- def test_setup_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- access_token_response = {
- error: 'invalid_grant',
- error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
- }.to_json
- @gateway.expects(:ssl_post).returns(access_token_response)
- @gateway.send(:acquire_access_token)
- end
-
- assert_match(/Failed with The provided authorization grant is invalid/, error.message)
- end
-
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 9b7a6f94a5b..7e48cce44ef 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,15 +32,6 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
- def test_refresh_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @oauth_2_gateway .send(:refresh_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb
index f47a31203a9..c120b2e99fe 100644
--- a/test/unit/gateways/simetrik_test.rb
+++ b/test/unit/gateways/simetrik_test.rb
@@ -1,5 +1,15 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SimetrikGateway < Gateway
+ def fetch_access_token
+ @access_token[:access_token] = SecureRandom.hex(16)
+ end
+ end
+ end
+end
+
class SimetrikTest < Test::Unit::TestCase
def setup
@token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205'
@@ -7,8 +17,7 @@ def setup
@gateway = SimetrikGateway.new(
client_id: 'client_id',
client_secret: 'client_secret_key',
- audience: 'audience_url',
- access_token: { expires_at: Time.new.to_i }
+ audience: 'audience_url'
)
@credit_card = CreditCard.new(
first_name: 'sergiod',
@@ -161,15 +170,6 @@ def test_success_purchase_with_billing_address
assert response.test?
end
- def test_fetch_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @gateway.send(:fetch_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_success_purchase_with_shipping_address
expected_body = JSON.parse(@authorize_capture_expected_body.dup)
expected_body['forward_payload']['order']['shipping_address'] = address
From ee2ac1a27d748b835e9d0689d7c42e00fa265b94 Mon Sep 17 00:00:00 2001
From: aenand
Date: Tue, 18 Jul 2023 13:01:55 -0400
Subject: [PATCH 146/390] CYBS: Recurring NT
Cybersource's legacy gateway supports recurring transactions for Network Tokens. The way
to accomplish this is to not send the `cryptogram` since that is one time use and mark the
`commerce_indicator` as `internet`.
Remote:
123 tests, 619 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
95.935% passed
5 tests failing on master
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 18 +++++--
.../gateways/remote_cyber_source_test.rb | 32 +++++++++++++
test/unit/gateways/cyber_source_test.rb | 47 +++++++++++++++++++
4 files changed, 93 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index bbbc58ec63e..155bc2f66d3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,7 @@
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
+* Cybersource: Support recurring transactions for NT [aenand] #4840
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index ec015454ddc..b8670353698 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -807,26 +807,34 @@ def network_tokenization?(payment_method)
payment_method.is_a?(NetworkTokenizationCreditCard)
end
+ def subsequent_nt_auth(options)
+ return unless options[:stored_credential] || options[:stored_credential_overrides]
+
+ options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
+ end
+
def add_auth_network_tokenization(xml, payment_method, options)
return unless network_tokenization?(payment_method)
+ commerce_indicator = 'internet' if subsequent_nt_auth(options)
+
brand = card_brand(payment_method).to_sym
case brand
when :visa
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('cavv', payment_method.payment_cryptogram)
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
- xml.tag!('xid', payment_method.payment_cryptogram)
+ xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
+ xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :master
xml.tag! 'ucaf' do
- xml.tag!('authenticationData', payment_method.payment_cryptogram)
+ xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
end
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :american_express
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 40277267975..a92a91895c5 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -740,6 +740,38 @@ def test_purchase_with_network_tokenization_with_amex_cc
assert_successful_response(auth)
end
+ def test_purchase_with_apple_pay_network_tokenization_visa_subsequent_auth
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+
+ assert auth = @gateway.purchase(@amount, credit_card, @options)
+ assert_successful_response(auth)
+ end
+
+ def test_purchase_with_apple_pay_network_tokenization_mastercard_subsequent_auth
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '0602MCC603474'
+ }
+
+ assert auth = @gateway.purchase(@amount, credit_card, @options)
+ assert_successful_response(auth)
+ end
+
def test_successful_authorize_with_mdd_fields
(1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" }
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 55505cc09a8..2e0519cc5e8 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -478,6 +478,53 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
assert_success response
end
+ def test_successful_network_token_purchase_subsequent_auth_visa
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_not_match %r'', request_body
+ assert_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram')
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_network_token_purchase_subsequent_auth_mastercard
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram')
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ end
+
def test_successful_reference_purchase
@gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_purchase_response)
From abec2c11bfc3be94f0a6c0fcc68323eb5fa900d6 Mon Sep 17 00:00:00 2001
From: aenand <89794007+aenand@users.noreply.github.com>
Date: Wed, 23 Aug 2023 15:05:59 -0400
Subject: [PATCH 147/390] Revert "CYBS: Recurring NT"
This reverts commit ee2ac1a27d748b835e9d0689d7c42e00fa265b94.
---
CHANGELOG | 1 -
.../billing/gateways/cyber_source.rb | 18 ++-----
.../gateways/remote_cyber_source_test.rb | 32 -------------
test/unit/gateways/cyber_source_test.rb | 47 -------------------
4 files changed, 5 insertions(+), 93 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 155bc2f66d3..bbbc58ec63e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,7 +20,6 @@
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
-* Cybersource: Support recurring transactions for NT [aenand] #4840
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index b8670353698..ec015454ddc 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -807,34 +807,26 @@ def network_tokenization?(payment_method)
payment_method.is_a?(NetworkTokenizationCreditCard)
end
- def subsequent_nt_auth(options)
- return unless options[:stored_credential] || options[:stored_credential_overrides]
-
- options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
- end
-
def add_auth_network_tokenization(xml, payment_method, options)
return unless network_tokenization?(payment_method)
- commerce_indicator = 'internet' if subsequent_nt_auth(options)
-
brand = card_brand(payment_method).to_sym
case brand
when :visa
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
- xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
- xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.tag!('cavv', payment_method.payment_cryptogram)
+ xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
+ xml.tag!('xid', payment_method.payment_cryptogram)
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :master
xml.tag! 'ucaf' do
- xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.tag!('authenticationData', payment_method.payment_cryptogram)
xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
end
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
+ xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :american_express
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index a92a91895c5..40277267975 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -740,38 +740,6 @@ def test_purchase_with_network_tokenization_with_amex_cc
assert_successful_response(auth)
end
- def test_purchase_with_apple_pay_network_tokenization_visa_subsequent_auth
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'visa',
- eci: '05',
- source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @options[:stored_credential] = {
- initiator: 'merchant',
- reason_type: 'unscheduled',
- network_transaction_id: '016150703802094'
- }
-
- assert auth = @gateway.purchase(@amount, credit_card, @options)
- assert_successful_response(auth)
- end
-
- def test_purchase_with_apple_pay_network_tokenization_mastercard_subsequent_auth
- credit_card = network_tokenization_credit_card('5555555555554444',
- brand: 'master',
- eci: '05',
- source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @options[:stored_credential] = {
- initiator: 'merchant',
- reason_type: 'unscheduled',
- network_transaction_id: '0602MCC603474'
- }
-
- assert auth = @gateway.purchase(@amount, credit_card, @options)
- assert_successful_response(auth)
- end
-
def test_successful_authorize_with_mdd_fields
(1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" }
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 2e0519cc5e8..55505cc09a8 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -478,53 +478,6 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
assert_success response
end
- def test_successful_network_token_purchase_subsequent_auth_visa
- @gateway.expects(:ssl_post).with do |_host, request_body|
- assert_not_match %r'', request_body
- assert_not_match %r'', request_body
- assert_match %r'internet', request_body
- true
- end.returns(successful_purchase_response)
-
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'visa',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram')
- options = @options.merge({
- stored_credential: {
- initiator: 'merchant',
- reason_type: 'unscheduled',
- network_transaction_id: '016150703802094'
- }
- })
- assert response = @gateway.purchase(@amount, credit_card, options)
- assert_success response
- end
-
- def test_successful_network_token_purchase_subsequent_auth_mastercard
- @gateway.expects(:ssl_post).with do |_host, request_body|
- assert_not_match %r'', request_body
- assert_match %r'internet', request_body
- true
- end.returns(successful_purchase_response)
-
- credit_card = network_tokenization_credit_card('5555555555554444',
- brand: 'master',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram')
- options = @options.merge({
- stored_credential: {
- initiator: 'merchant',
- reason_type: 'unscheduled',
- network_transaction_id: '016150703802094'
- }
- })
- assert response = @gateway.purchase(@amount, credit_card, options)
- assert_success response
- end
-
def test_successful_reference_purchase
@gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_purchase_response)
From 4289353c414b0fcc0e68b16aa9b6b994d26db0f2 Mon Sep 17 00:00:00 2001
From: khoi_nguyen_deepstack
Date: Wed, 9 Aug 2023 17:09:42 -0700
Subject: [PATCH 148/390] Deepstack: Update sandbox credentials
---
test/fixtures.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 2901eace4c0..77fda8bf7ce 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -310,8 +310,8 @@ decidir_purchase:
deepstack:
publishable_api_key: pk_test_7H5GkZJ4ktV38eZxKDItVMZZvluUhORE
- app_id: afa25dea-58e3-4da0-a51e-bb6dca949c90
- shared_secret: 30lYhQ92gREPrwaHOAkyubaIqi88wV8PYy4+2AWJ2to=
+ app_id: sk_test_8fe27907-c359-4fe4-ad9b-eaaa
+ shared_secret: JC6zgUX3oZ9vRshFsM98lXzH4tu6j4ZfB4cSOqOX/xQ=
# No working test credentials
dibs:
From ef367ec17fde778fe319946178f14f90fa13a185 Mon Sep 17 00:00:00 2001
From: aenand
Date: Thu, 24 Aug 2023 10:25:05 -0400
Subject: [PATCH 149/390] Release v1.135.0
---
CHANGELOG | 2 ++
lib/active_merchant/version.rb | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index bbbc58ec63e..5a5fd7613e0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@
= ActiveMerchant CHANGELOG
== HEAD
+
+== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
* Adyen: Add option to elect which error message [aenand] #4843
* Reach: Update list of supported countries [jcreiff] #4842
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index 4fa45514146..68017e31781 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.134.0'
+ VERSION = '1.135.0'
end
From f6b99f14da5c625ac5a2c0aa64424ff00f247c4e Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 18 Aug 2023 13:25:10 -0500
Subject: [PATCH 150/390] Braintree: Add sca_exemption
To specifiy the SCA exemption that a trasaction will be claming.
Remote:
105 tests, 559 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
94 tests, 207 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/braintree_blue.rb | 2 +
.../gateways/remote_braintree_blue_test.rb | 9 +++--
test/unit/gateways/braintree_blue_test.rb | 37 +++++++++++++------
4 files changed, 34 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5a5fd7613e0..396fd310c1d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -22,6 +22,7 @@
* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
+* Braintree: Add sca_exemption [almalee24] #4864
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 1da4cacb229..cb49c767190 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -678,6 +678,8 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
add_3ds_info(parameters, options[:three_d_secure])
+ parameters[:sca_exemption] = options[:three_ds_exemption_type] if options[:three_ds_exemption_type]
+
if options[:payment_method_nonce].is_a?(String)
parameters.delete(:customer)
parameters[:payment_method_nonce] = options[:payment_method_nonce]
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index ea68f247188..4cfd400a67d 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -638,9 +638,12 @@ def test_successful_purchase_with_addresses
assert_equal 'Mexico', transaction['shipping_details']['country_name']
end
- def test_successful_purchase_with_three_d_secure_pass_thru
- three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' }
- response = @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_params)
+ def test_successful_purchase_with_three_d_secure_pass_thru_and_sca_exemption
+ options = {
+ three_ds_exemption_type: 'low_value',
+ three_d_secure: { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' }
+ }
+ response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
end
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index ab95429829e..14662a8e1bc 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -746,21 +746,34 @@ def test_three_d_secure_pass_thru_handling_version_1
end
def test_three_d_secure_pass_thru_handling_version_2
- Braintree::TransactionGateway.
- any_instance.
- expects(:sale).
- with(has_entries(three_d_secure_pass_thru: has_entries(
- three_d_secure_version: '2.0',
+ three_ds_expectation = {
+ three_d_secure_version: '2.0',
+ cavv: 'cavv',
+ eci_flag: 'eci',
+ ds_transaction_id: 'trans_id',
+ cavv_algorithm: 'algorithm',
+ directory_response: 'directory',
+ authentication_response: 'auth'
+ }
+
+ Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
+ (params[:sca_exemption] == 'low_value')
+ (params[:three_d_secure_pass_thru] == three_ds_expectation)
+ end.returns(braintree_result)
+
+ options = {
+ three_ds_exemption_type: 'low_value',
+ three_d_secure: {
+ version: '2.0',
cavv: 'cavv',
- eci_flag: 'eci',
+ eci: 'eci',
ds_transaction_id: 'trans_id',
cavv_algorithm: 'algorithm',
- directory_response: 'directory',
- authentication_response: 'auth'
- ))).
- returns(braintree_result)
-
- @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: { version: '2.0', cavv: 'cavv', eci: 'eci', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' })
+ directory_response_status: 'directory',
+ authentication_response_status: 'auth'
+ }
+ }
+ @gateway.purchase(100, credit_card('41111111111111111111'), options)
end
def test_three_d_secure_pass_thru_some_fields
From 66e43051b03496ad79cb5f1fb03d9569bf404f15 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 21 Aug 2023 14:23:06 -0500
Subject: [PATCH 151/390] Ebanx: Update Verify
Update Verify to use verifycard endpoint.
Unit:
19 tests, 85 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
32 tests, 88 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96.875% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ebanx.rb | 74 ++++++++++---------
test/remote/gateways/remote_ebanx_test.rb | 5 +-
test/unit/gateways/ebanx_test.rb | 26 ++++---
4 files changed, 58 insertions(+), 48 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 396fd310c1d..1755a3033e4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -23,6 +23,7 @@
* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
* Braintree: Add sca_exemption [almalee24] #4864
+* Ebanx: Update Verify [almalee24] #4866
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index d0381245158..d9233cc0841 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -20,7 +20,8 @@ class EbanxGateway < Gateway
refund: 'refund',
void: 'cancel',
store: 'token',
- inquire: 'query'
+ inquire: 'query',
+ verify: 'verifycard'
}
HTTP_METHOD = {
@@ -30,16 +31,8 @@ class EbanxGateway < Gateway
refund: :post,
void: :get,
store: :post,
- inquire: :get
- }
-
- VERIFY_AMOUNT_PER_COUNTRY = {
- 'br' => 100,
- 'ar' => 100,
- 'co' => 50000,
- 'pe' => 300,
- 'mx' => 2000,
- 'cl' => 80000
+ inquire: :get,
+ verify: :post
}
def initialize(options = {})
@@ -107,17 +100,22 @@ def void(authorization, options = {})
def store(credit_card, options = {})
post = {}
add_integration_key(post)
- add_payment_details(post, credit_card)
- post[:country] = customer_country(options)
+ customer_country(post, options)
+ add_payment_type(post)
+ post[:creditcard] = payment_details(credit_card)
commit(:store, post)
end
def verify(credit_card, options = {})
- MultiResponse.run(:use_first_response) do |r|
- r.process { authorize(VERIFY_AMOUNT_PER_COUNTRY[customer_country(options)], credit_card, options) }
- r.process(:ignore_result) { void(r.authorization, options) }
- end
+ post = {}
+ add_integration_key(post)
+ add_payment_type(post)
+ customer_country(post, options)
+ post[:card] = payment_details(credit_card)
+ post[:device_id] = options[:device_id] if options[:device_id]
+
+ commit(:verify, post)
end
def inquire(authorization, options = {})
@@ -192,14 +190,13 @@ def add_invoice(post, money, options)
def add_card_or_token(post, payment, options)
payment = payment.split('|')[0] if payment.is_a?(String)
- post[:payment][:payment_type_code] = 'creditcard'
+ add_payment_type(post[:payment])
post[:payment][:creditcard] = payment_details(payment)
post[:payment][:creditcard][:soft_descriptor] = options[:soft_descriptor] if options[:soft_descriptor]
end
- def add_payment_details(post, payment)
+ def add_payment_type(post)
post[:payment_type_code] = 'creditcard'
- post[:creditcard] = payment_details(payment)
end
def payment_details(payment)
@@ -237,7 +234,7 @@ def commit(action, parameters)
Response.new(
success,
- message_from(response),
+ message_from(action, response),
response,
authorization: authorization_from(action, parameters, response),
test: test?,
@@ -259,25 +256,32 @@ def add_processing_type_to_commit_headers(commit_headers, processing_type)
end
def success_from(action, response)
- payment_status = response.try(:[], 'payment').try(:[], 'status')
-
- if %i[purchase capture refund].include?(action)
- payment_status == 'CO'
- elsif action == :authorize
- payment_status == 'PE'
- elsif action == :void
- payment_status == 'CA'
- elsif %i[store inquire].include?(action)
- response.try(:[], 'status') == 'SUCCESS'
+ status = response.dig('payment', 'status')
+
+ case action
+ when :purchase, :capture, :refund
+ status == 'CO'
+ when :authorize
+ status == 'PE'
+ when :void
+ status == 'CA'
+ when :verify
+ response.dig('card_verification', 'transaction_status', 'code') == 'OK'
+ when :store, :inquire
+ response.dig('status') == 'SUCCESS'
else
false
end
end
- def message_from(response)
+ def message_from(action, response)
return response['status_message'] if response['status'] == 'ERROR'
- response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'description')
+ if action == :verify
+ response.dig('card_verification', 'transaction_status', 'description')
+ else
+ response.dig('payment', 'transaction_status', 'description')
+ end
end
def authorization_from(action, parameters, response)
@@ -327,9 +331,9 @@ def error_code_from(response, success)
end
end
- def customer_country(options)
+ def customer_country(post, options)
if country = options[:country] || (options[:billing_address][:country] if options[:billing_address])
- country.downcase
+ post[:country] = country.downcase
end
end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
index 4ac5176ce55..266c7b4e2ed 100644
--- a/test/remote/gateways/remote_ebanx_test.rb
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -302,9 +302,10 @@ def test_successful_verify_for_mexico
end
def test_failed_verify
- response = @gateway.verify(@declined_card, @options)
+ declined_card = credit_card('6011088896715918')
+ response = @gateway.verify(declined_card, @options)
assert_failure response
- assert_match %r{Invalid card or card type}, response.message
+ assert_match %r{Not accepted}, response.message
end
def test_successful_inquire
diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb
index 500707694a1..423a1c0f83f 100644
--- a/test/unit/gateways/ebanx_test.rb
+++ b/test/unit/gateways/ebanx_test.rb
@@ -136,15 +136,7 @@ def test_failed_void
end
def test_successful_verify
- @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, successful_void_response)
-
- response = @gateway.verify(@credit_card, @options)
- assert_success response
- assert_equal nil, response.error_code
- end
-
- def test_successful_verify_with_failed_void
- @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, failed_void_response)
+ @gateway.expects(:ssl_request).returns(successful_verify_response)
response = @gateway.verify(@credit_card, @options)
assert_success response
@@ -152,11 +144,11 @@ def test_successful_verify_with_failed_void
end
def test_failed_verify
- @gateway.expects(:ssl_request).returns(failed_authorize_response)
+ @gateway.expects(:ssl_request).returns(failed_verify_response)
response = @gateway.verify(@credit_card, @options)
assert_failure response
- assert_equal 'NOK', response.error_code
+ assert_equal 'Not accepted', response.message
end
def test_successful_store_and_purchase
@@ -235,6 +227,18 @@ def failed_authorize_response
)
end
+ def successful_verify_response
+ %(
+ {"status":"SUCCESS","payment_type_code":"creditcard","card_verification":{"transaction_status":{"code":"OK","description":"Accepted"},"transaction_type":"ZERO DOLLAR"}}
+ )
+ end
+
+ def failed_verify_response
+ %(
+ {"status":"SUCCESS","payment_type_code":"discover","card_verification":{"transaction_status":{"code":"NOK", "description":"Not accepted"}, "transaction_type":"GHOST AUTHORIZATION"}}
+ )
+ end
+
def successful_capture_response
%(
{"payment":{"hash":"5dee94502bd59660b801c441ad5a703f2c4123f5fc892ccb","pin":"675968133","country":"br","merchant_payment_code":"b98b2892b80771b9dadf2ebc482cb65d","order_number":null,"status":"CO","status_date":"2019-12-09 18:37:05","open_date":"2019-12-09 18:37:04","confirm_date":"2019-12-09 18:37:05","transfer_date":null,"amount_br":"4.19","amount_ext":"1.00","amount_iof":"0.02","currency_rate":"4.1700","currency_ext":"USD","due_date":"2019-12-12","instalments":"1","payment_type_code":"visa","details":{"billing_descriptor":"DEMONSTRATION"},"transaction_status":{"acquirer":"EBANX","code":"OK","description":"Accepted"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"}
From 461ff346ece451c3df44848d4fb8805d473a9b5c Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Tue, 29 Aug 2023 10:33:19 -0500
Subject: [PATCH 152/390] Shift4_v2: Inherit securionPay API to enable Shift4v2
(#4860)
Description
-------------------------
[SER-653](https://spreedly.atlassian.net/browse/SER-653)
[SER-654](https://spreedly.atlassian.net/browse/SER-654)
[SER-655](https://spreedly.atlassian.net/browse/SER-655)
[SER-662](https://spreedly.atlassian.net/browse/SER-662)
Shift4 purchased Securion Pay and is now using their API, that's why
this commit enable a new shift4_v2 gateway
Unit test
-------------------------
Finished in 0.150258 seconds.
34 tests, 191 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
Finished in 28.137188 seconds.
30 tests, 103 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Rubocop
-------------------------
760 files inspected, no offenses detected
Co-authored-by: cristian
---
CHANGELOG | 1 +
.../billing/gateways/securion_pay.rb | 20 ++++-
.../billing/gateways/shift4_v2.rb | 53 ++++++++++++
test/fixtures.yml | 3 +
.../gateways/remote_securion_pay_test.rb | 35 +++++---
test/remote/gateways/remote_shift4_v2_test.rb | 80 +++++++++++++++++
test/unit/gateways/securion_pay_test.rb | 4 +-
test/unit/gateways/shift4_v2_test.rb | 85 +++++++++++++++++++
8 files changed, 262 insertions(+), 19 deletions(-)
create mode 100644 lib/active_merchant/billing/gateways/shift4_v2.rb
create mode 100644 test/remote/gateways/remote_shift4_v2_test.rb
create mode 100644 test/unit/gateways/shift4_v2_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index 1755a3033e4..a273faae467 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb
index 94663c5b58b..0489ec924dd 100644
--- a/lib/active_merchant/billing/gateways/securion_pay.rb
+++ b/lib/active_merchant/billing/gateways/securion_pay.rb
@@ -223,18 +223,30 @@ def commit(url, parameters = nil, options = {}, method = nil)
end
response = api_request(url, parameters, options, method)
- success = !response.key?('error')
+ success = success?(response)
Response.new(
success,
(success ? 'Transaction approved' : response['error']['message']),
response,
test: test?,
- authorization: (success ? response['id'] : response['error']['charge']),
+ authorization: authorization_from(url, response),
error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']])
)
end
+ def authorization_from(action, response)
+ if action == 'customers' && success?(response) && response['cards'].present?
+ response['cards'].first['id']
+ else
+ success?(response) ? response['id'] : response['error']['charge']
+ end
+ end
+
+ def success?(response)
+ !response.key?('error')
+ end
+
def headers(options = {})
secret_key = options[:secret_key] || @options[:secret_key]
@@ -289,8 +301,8 @@ def api_request(endpoint, parameters = nil, options = {}, method = nil)
response
end
- def json_error(raw_response)
- msg = 'Invalid response received from the SecurionPay API.'
+ def json_error(raw_response, gateway_name = 'SecurionPay')
+ msg = "Invalid response received from the #{gateway_name} API."
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{
'error' => {
diff --git a/lib/active_merchant/billing/gateways/shift4_v2.rb b/lib/active_merchant/billing/gateways/shift4_v2.rb
new file mode 100644
index 00000000000..536b779dfb3
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/shift4_v2.rb
@@ -0,0 +1,53 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class Shift4V2Gateway < SecurionPayGateway
+ # same endpont for testing
+ self.live_url = 'https://api.shift4.com/'
+ self.display_name = 'Shift4'
+ self.homepage_url = 'https://dev.shift4.com/us/'
+
+ def credit(money, payment, options = {})
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ commit('credits', post, options)
+ end
+
+ def create_post_for_auth_or_purchase(money, payment, options)
+ super.tap do |post|
+ add_stored_credentials(post, options)
+ end
+ end
+
+ def add_stored_credentials(post, options)
+ return unless options[:stored_credential].present?
+
+ initiator = options.dig(:stored_credential, :initiator)
+ reason_type = options.dig(:stored_credential, :reason_type)
+
+ post_type = {
+ %w[cardholder recurring] => 'first_recurring',
+ %w[merchant recurring] => 'subsequent_recurring',
+ %w[cardholder unscheduled] => 'customer_initiated',
+ %w[merchant installment] => 'merchant_initiated'
+ }[[initiator, reason_type]]
+ post[:type] = post_type if post_type
+ end
+
+ def headers(options = {})
+ super.tap do |headers|
+ headers['User-Agent'] = "Shift4/v2 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
+ end
+ end
+
+ def scrub(transcript)
+ super.
+ gsub(%r((card\[expMonth\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[expYear\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[cardholderName\]=)\w+[^ ]\w+), '\1[FILTERED]')
+ end
+
+ def json_error(raw_response)
+ super(raw_response, 'Shift4 V2')
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 77fda8bf7ce..c1db9c47cbf 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -1291,6 +1291,9 @@ shift4:
client_guid: YOUR_CLIENT_ID
auth_token: YOUR_AUTH_TOKEN
+shift4_v2:
+ secret_key: pr_test_xxxxxxxxx
+
# Working credentials, no need to replace
simetrik:
client_id: 'wNhJBdrKDk3vTmkQMAWi5zWN7y21adO3'
diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb
index b2ffead799f..6e83fa91d76 100644
--- a/test/remote/gateways/remote_securion_pay_test.rb
+++ b/test/remote/gateways/remote_securion_pay_test.rb
@@ -20,15 +20,28 @@ def setup
}
end
+ def test_successful_store_and_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'customer', response.params['objectType']
+ assert_match %r(^card_\w+$), response.params['cards'][0]['id']
+ assert_equal 'card', response.params['cards'][0]['objectType']
+
+ @options[:customer_id] = response.params['cards'][0]['customerId']
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
def test_successful_store
response = @gateway.store(@credit_card, @options)
assert_success response
- assert_match %r(^cust_\w+$), response.authorization
assert_equal 'customer', response.params['objectType']
assert_match %r(^card_\w+$), response.params['cards'][0]['id']
assert_equal 'card', response.params['cards'][0]['objectType']
- @options[:customer_id] = response.authorization
+ @options[:customer_id] = response.params['cards'][0]['customerId']
response = @gateway.store(@new_credit_card, @options)
assert_success response
assert_match %r(^card_\w+$), response.params['card']['id']
@@ -43,11 +56,6 @@ def test_successful_store
assert_equal '4242', response.params['cards'][1]['last4']
end
- # def test_dump_transcript
- # skip("Transcript scrubbing for this gateway has been tested.")
- # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
- # end
-
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @credit_card, @options)
@@ -81,7 +89,7 @@ def test_successful_purchase_with_three_ds_data
def test_unsuccessful_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match %r{The card was declined for other reason.}, response.message
+ assert_match %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
end
@@ -104,7 +112,7 @@ def test_failed_authorize
def test_failed_capture
response = @gateway.capture(@amount, 'invalid_authorization_token')
assert_failure response
- assert_match %r{Requested Charge does not exist}, response.message
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_successful_full_refund
@@ -116,7 +124,7 @@ def test_successful_full_refund
assert_success refund
assert refund.params['refunded']
- assert_equal 0, refund.params['amount']
+ assert_equal 2000, refund.params['refunds'].first['amount']
assert_equal 1, refund.params['refunds'].size
assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum
@@ -130,6 +138,7 @@ def test_successful_partially_refund
first_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success first_refund
+ assert_equal @refund_amount, first_refund.params['refunds'].first['amount']
second_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success second_refund
@@ -143,7 +152,7 @@ def test_successful_partially_refund
def test_unsuccessful_authorize_refund
response = @gateway.refund(@amount, 'invalid_authorization_token')
assert_failure response
- assert_match %r{Requested Charge does not exist}, response.message
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_unsuccessful_refund
@@ -173,7 +182,7 @@ def test_successful_void
def test_failed_void
response = @gateway.void('invalid_authorization_token', @options)
assert_failure response
- assert_match %r{Requested Charge does not exist}, response.message
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_successful_verify
@@ -185,7 +194,7 @@ def test_successful_verify
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
- assert_match %r{The card was declined for other reason.}, response.message
+ assert_match %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.primary_response.error_code
end
diff --git a/test/remote/gateways/remote_shift4_v2_test.rb b/test/remote/gateways/remote_shift4_v2_test.rb
new file mode 100644
index 00000000000..7d501b34dd3
--- /dev/null
+++ b/test/remote/gateways/remote_shift4_v2_test.rb
@@ -0,0 +1,80 @@
+require 'test_helper'
+require_relative 'remote_securion_pay_test'
+
+class RemoteShift4V2Test < RemoteSecurionPayTest
+ def setup
+ super
+ @gateway = Shift4V2Gateway.new(fixtures(:shift4_v2))
+ end
+
+ def test_successful_purchase_third_party_token
+ auth = @gateway.store(@credit_card, @options)
+ token = auth.params['defaultCardId']
+ customer_id = auth.params['id']
+ response = @gateway.purchase(@amount, token, @options.merge!(customer_id: customer_id))
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'foo@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_unsuccessful_purchase_third_party_token
+ auth = @gateway.store(@credit_card, @options)
+ customer_id = auth.params['id']
+ response = @gateway.purchase(@amount, @invalid_token, @options.merge!(customer_id: customer_id))
+ assert_failure response
+ assert_equal "Token 'tok_invalid' does not exist", response.message
+ end
+
+ def test_successful_stored_credentials_first_recurring
+ stored_credentials = {
+ initiator: 'cardholder',
+ reason_type: 'recurring'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'first_recurring', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_subsequent_recurring
+ stored_credentials = {
+ initiator: 'merchant',
+ reason_type: 'recurring'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'subsequent_recurring', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_customer_initiated
+ stored_credentials = {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'customer_initiated', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_merchant_initiated
+ stored_credentials = {
+ initiator: 'merchant',
+ reason_type: 'installment'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'merchant_initiated', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+end
diff --git a/test/unit/gateways/securion_pay_test.rb b/test/unit/gateways/securion_pay_test.rb
index 020dbdf4b27..e812b4c8a84 100644
--- a/test/unit/gateways/securion_pay_test.rb
+++ b/test/unit/gateways/securion_pay_test.rb
@@ -36,7 +36,6 @@ def test_successful_store
response = @gateway.store(@credit_card, @options)
assert_success response
- assert_match %r(^cust_\w+$), response.authorization
assert_equal 'customer', response.params['objectType']
assert_match %r(^card_\w+$), response.params['cards'][0]['id']
assert_equal 'card', response.params['cards'][0]['objectType']
@@ -44,7 +43,8 @@ def test_successful_store
@gateway.expects(:ssl_post).returns(successful_authorize_response)
@gateway.expects(:ssl_post).returns(successful_void_response)
- @options[:customer_id] = response.authorization
+ @options[:customer_id] = response.params['cards'][0]['customerId']
+
response = @gateway.store(@new_credit_card, @options)
assert_success response
assert_match %r(^card_\w+$), response.params['card']['id']
diff --git a/test/unit/gateways/shift4_v2_test.rb b/test/unit/gateways/shift4_v2_test.rb
new file mode 100644
index 00000000000..1c97361dd7b
--- /dev/null
+++ b/test/unit/gateways/shift4_v2_test.rb
@@ -0,0 +1,85 @@
+require 'test_helper'
+require_relative 'securion_pay_test'
+
+class Shift4V2Test < SecurionPayTest
+ include CommStub
+
+ def setup
+ super
+ @gateway = Shift4V2Gateway.new(
+ secret_key: 'pr_test_random_key'
+ )
+ end
+
+ def test_invalid_raw_response
+ @gateway.expects(:ssl_request).returns(invalid_json_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match(/^Invalid response received from the Shift4 V2 API/, response.message)
+ end
+
+ def test_ensure_does_not_respond_to_credit; end
+
+ private
+
+ def pre_scrubbed
+ <<-PRE_SCRUBBED
+ opening connection to api.shift4.com:443...
+ opened
+ starting SSL for api.shift4.com:443...
+ SSL established
+ <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic cHJfdGVzdF9xWk40VlZJS0N5U2ZDZVhDQm9ITzlEQmU6\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n"
+ <- "amount=2000¤cy=usd&card[number]=4242424242424242&card[expMonth]=9&card[expYear]=2016&card[cvc]=123&card[cardholderName]=Longbob+Longsen&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Server: cloudflare-nginx\r\n"
+ -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n"
+ -> "Content-Type: application/json;charset=UTF-8\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Connection: close\r\n"
+ -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n"
+ -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n"
+ -> "\r\n"
+ -> "1f4\r\n"
+ reading 500 bytes...
+ -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}"
+ read 500 bytes
+ reading 2 bytes...
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed
+ <<-POST_SCRUBBED
+ opening connection to api.shift4.com:443...
+ opened
+ starting SSL for api.shift4.com:443...
+ SSL established
+ <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n"
+ <- "amount=2000¤cy=usd&card[number]=[FILTERED]&card[expMonth]=[FILTERED]&card[expYear]=[FILTERED]&card[cvc]=[FILTERED]&card[cardholderName]=[FILTERED]&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Server: cloudflare-nginx\r\n"
+ -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n"
+ -> "Content-Type: application/json;charset=UTF-8\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Connection: close\r\n"
+ -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n"
+ -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n"
+ -> "\r\n"
+ -> "1f4\r\n"
+ reading 500 bytes...
+ -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}"
+ read 500 bytes
+ reading 2 bytes...
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ POST_SCRUBBED
+ end
+end
From 3d007ed93aca8ddcb28304fc8d0a16ad6c8cd0ec Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Fri, 1 Sep 2023 10:37:33 -0500
Subject: [PATCH 153/390] Rapyd: 3ds gateway specific (#4876)
Description
-------------------------
This commit add a couple of test to be sure that we are sending
the three_d_required field as a GSF in order to perform a 3DS gateway specific in Rapyd.
Additional enable execute 3ds gateway specific when the execute_threed option is true.
Unit test
-------------------------
Finished in 0.076246 seconds.
27 tests, 140 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
354.12 tests/s, 1823.05 assertions/s
Remote test
-------------------------
Finished in 113.329864 seconds.
34 tests, 99 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.0588% passed
0.30 tests/s, 0.86 assertions/s
Rubocop
-------------------------
763 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
test/remote/gateways/remote_rapyd_test.rb | 22 ++++++++++++++++++++++
test/unit/gateways/rapyd_test.rb | 16 ++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index e6dee2d82c7..8be0c3002af 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -323,4 +323,26 @@ def test_successful_authorize_with_3ds_v2_options
assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
assert response.params['data']['redirect_url']
end
+
+ def test_successful_purchase_with_3ds_v2_gateway_specific
+ options = @options.merge(three_d_secure: { required: true })
+ options[:pm_type] = 'gb_visa_card'
+
+ response = @gateway.purchase(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ assert response.params['data']['redirect_url']
+ assert_match 'https://sandboxcheckout.rapyd.net/3ds-payment?token=payment_', response.params['data']['redirect_url']
+ end
+
+ def test_successful_purchase_without_3ds_v2_gateway_specific
+ options = @options.merge(three_d_secure: { required: false })
+ options[:pm_type] = 'gb_visa_card'
+ response = @gateway.purchase(1000, @credit_card, options)
+ assert_success response
+ assert_equal 'CLO', response.params['data']['status']
+ assert_equal 'not_applicable', response.params['data']['payment_method_data']['next_action']
+ assert_equal '', response.params['data']['redirect_url']
+ end
end
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index fd3cd6b3ba2..03070aa1b33 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -122,6 +122,22 @@ def test_successful_purchase_with_stored_credential
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_3ds_gateway_specific
+ @options[:three_d_secure] = {
+ required: true,
+ version: '2.1.0'
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['payment_method_options']['3d_required'], true
+ assert_equal request['payment_method_options']['3d_version'], '2.1.0'
+ assert request['complete_payment_url']
+ assert request['error_payment_url']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_request).returns(failed_purchase_response)
From 4b7265bc4da6d7bf9a638665adebfaf8b0bbe405 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 30 Aug 2023 12:27:07 -0700
Subject: [PATCH 154/390] TNS: Use the customer specified order_id in the
request
Local:
5588 tests, 77757 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications
99.66% passed
Unit:
15 tests, 68 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
13 tests, 42 assertions, 2 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications
76.9231% passed
Note: I removed the test_verify_credentials because even the 'unknown' fixture creds were returning 400 response codes which is the same as the actual fixures
---
CHANGELOG | 1 +
.../billing/gateways/mastercard.rb | 8 ++++----
test/remote/gateways/remote_tns_test.rb | 15 ++++-----------
3 files changed, 9 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a273faae467..8e27176679b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
== HEAD
* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
+* TNS: Use the specified order_id in request if available [yunnydang] #4880
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/mastercard.rb b/lib/active_merchant/billing/gateways/mastercard.rb
index ffbb2e3fb67..894ad2be3f5 100644
--- a/lib/active_merchant/billing/gateways/mastercard.rb
+++ b/lib/active_merchant/billing/gateways/mastercard.rb
@@ -10,7 +10,7 @@ def purchase(amount, payment_method, options = {})
if options[:pay_mode]
post = new_post
add_invoice(post, amount, options)
- add_reference(post, *new_authorization)
+ add_reference(post, *new_authorization(options))
add_payment_method(post, payment_method)
add_customer_data(post, payment_method, options)
add_3dsecure_id(post, options)
@@ -27,7 +27,7 @@ def purchase(amount, payment_method, options = {})
def authorize(amount, payment_method, options = {})
post = new_post
add_invoice(post, amount, options)
- add_reference(post, *new_authorization)
+ add_reference(post, *new_authorization(options))
add_payment_method(post, payment_method)
add_customer_data(post, payment_method, options)
add_3dsecure_id(post, options)
@@ -264,9 +264,9 @@ def split_authorization(authorization)
authorization.split('|')
end
- def new_authorization
+ def new_authorization(options)
# Must be unique within a merchant id.
- orderid = SecureRandom.uuid
+ orderid = options[:order_id] || SecureRandom.uuid
# Must be unique within an order id.
transactionid = '1'
diff --git a/test/remote/gateways/remote_tns_test.rb b/test/remote/gateways/remote_tns_test.rb
index 6155f16f5fa..f515c9854ff 100644
--- a/test/remote/gateways/remote_tns_test.rb
+++ b/test/remote/gateways/remote_tns_test.rb
@@ -6,8 +6,8 @@ def setup
@gateway = TnsGateway.new(fixtures(:tns))
@amount = 100
- @credit_card = credit_card('5123456789012346', month: 05, year: 2021)
- @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2021)
+ @credit_card = credit_card('5123456789012346', month: 05, year: 2024)
+ @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2024)
@declined_card = credit_card('5123456789012346', month: 01, year: 2028)
@options = {
@@ -67,7 +67,7 @@ def test_successful_purchase_with_region
def test_failed_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'FAILURE - DECLINED', response.message
+ assert_equal 'FAILURE - UNSPECIFIED_FAILURE', response.message
end
def test_successful_authorize_and_capture
@@ -84,7 +84,7 @@ def test_successful_authorize_and_capture
def test_failed_authorize
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'FAILURE - DECLINED', response.message
+ assert_equal 'FAILURE - UNSPECIFIED_FAILURE', response.message
end
def test_successful_refund
@@ -134,11 +134,4 @@ def test_transcript_scrubbing
assert_scrubbed(card.verification_value, transcript)
assert_scrubbed(@gateway.options[:password], transcript)
end
-
- def test_verify_credentials
- assert @gateway.verify_credentials
-
- gateway = TnsGateway.new(userid: 'unknown', password: 'unknown')
- assert !gateway.verify_credentials
- end
end
From 587795e407cffd6db510c1412efcf508820fb38e Mon Sep 17 00:00:00 2001
From: aenand
Date: Tue, 18 Jul 2023 13:01:55 -0400
Subject: [PATCH 155/390] CYBS: Recurring NT
Cybersource's legacy gateway supports recurring transactions for Network Tokens. The way
to accomplish this is to not send the `cryptogram` since that is one time use and mark the
`commerce_indicator` as `internet`.
Remote:
123 tests, 619 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
95.935% passed
5 tests failing on master
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 19 ++-
.../gateways/remote_cyber_source_test.rb | 80 ++++++-----
test/unit/gateways/cyber_source_test.rb | 133 +++++++++++-------
4 files changed, 149 insertions(+), 84 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8e27176679b..33d7e9d4282 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
== HEAD
* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
* TNS: Use the specified order_id in request if available [yunnydang] #4880
+* Cybersource: Support recurring apple pay [aenand] #4874
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index ec015454ddc..387b89863be 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -807,26 +807,35 @@ def network_tokenization?(payment_method)
payment_method.is_a?(NetworkTokenizationCreditCard)
end
+ def subsequent_nt_apple_pay_auth(source, options)
+ return unless options[:stored_credential] || options[:stored_credential_overrides]
+ return unless @@payment_solution[source]
+
+ options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
+ end
+
def add_auth_network_tokenization(xml, payment_method, options)
return unless network_tokenization?(payment_method)
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
+
brand = card_brand(payment_method).to_sym
case brand
when :visa
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('cavv', payment_method.payment_cryptogram)
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
- xml.tag!('xid', payment_method.payment_cryptogram)
+ xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
+ xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :master
xml.tag! 'ucaf' do
- xml.tag!('authenticationData', payment_method.payment_cryptogram)
+ xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
end
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :american_express
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 40277267975..14d9ca65e31 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -53,6 +53,18 @@ def setup
year: (Time.now.year + 2).to_s,
brand: :master
)
+ @visa_network_token = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @amex_network_token = network_tokenization_credit_card(
+ '378282246310005',
+ brand: 'american_express',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@amount = 100
@@ -118,20 +130,13 @@ def test_transcript_scrubbing
end
def test_network_tokenization_transcript_scrubbing
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- )
-
transcript = capture_transcript(@gateway) do
- @gateway.authorize(@amount, credit_card, @options)
+ @gateway.authorize(@amount, @visa_network_token, @options)
end
transcript = @gateway.scrub(transcript)
- assert_scrubbed(credit_card.number, transcript)
- assert_scrubbed(credit_card.payment_cryptogram, transcript)
+ assert_scrubbed(@visa_network_token.number, transcript)
+ assert_scrubbed(@visa_network_token.payment_cryptogram, transcript)
assert_scrubbed(@gateway.options[:password], transcript)
end
@@ -681,14 +686,7 @@ def test_successful_refund_with_bank_account_follow_on
end
def test_network_tokenization_authorize_and_capture
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- )
-
- assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @visa_network_token, @options)
assert_successful_response(auth)
assert capture = @gateway.capture(@amount, auth.authorization)
@@ -696,14 +694,7 @@ def test_network_tokenization_authorize_and_capture
end
def test_network_tokenization_with_amex_cc_and_basic_cryptogram
- credit_card = network_tokenization_credit_card(
- '378282246310005',
- brand: 'american_express',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- )
-
- assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @amex_network_token, @options)
assert_successful_response(auth)
assert capture = @gateway.capture(@amount, auth.authorization)
@@ -729,12 +720,37 @@ def test_network_tokenization_with_amex_cc_longer_cryptogram
end
def test_purchase_with_network_tokenization_with_amex_cc
- credit_card = network_tokenization_credit_card(
- '378282246310005',
- brand: 'american_express',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- )
+ assert auth = @gateway.purchase(@amount, @amex_network_token, @options)
+ assert_successful_response(auth)
+ end
+
+ def test_purchase_with_apple_pay_network_tokenization_visa_subsequent_auth
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+
+ assert auth = @gateway.purchase(@amount, credit_card, @options)
+ assert_successful_response(auth)
+ end
+
+ def test_purchase_with_apple_pay_network_tokenization_mastercard_subsequent_auth
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '0602MCC603474'
+ }
assert auth = @gateway.purchase(@amount, credit_card, @options)
assert_successful_response(auth)
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 55505cc09a8..9efca5a9cc9 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -18,6 +18,19 @@ def setup
@master_credit_card = credit_card('4111111111111111', brand: 'master')
@elo_credit_card = credit_card('5067310000000010', brand: 'elo')
@declined_card = credit_card('801111111111111', brand: 'visa')
+ @network_token = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :network_token)
+ @apple_pay = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :apple_pay)
+ @google_pay = network_tokenization_credit_card('4242424242424242', source: :google_pay)
@check = check()
@options = {
@@ -217,7 +230,7 @@ def test_purchase_includes_invoice_header
def test_purchase_with_apple_pay_includes_payment_solution_001
stub_comms do
- @gateway.purchase(100, network_tokenization_credit_card('4242424242424242', source: :apple_pay))
+ @gateway.purchase(100, @apple_pay)
end.check_request do |_endpoint, data, _headers|
assert_match(/001<\/paymentSolution>/, data)
end.respond_with(successful_purchase_response)
@@ -225,7 +238,7 @@ def test_purchase_with_apple_pay_includes_payment_solution_001
def test_purchase_with_google_pay_includes_payment_solution_012
stub_comms do
- @gateway.purchase(100, network_tokenization_credit_card('4242424242424242', source: :google_pay))
+ @gateway.purchase(100, @google_pay)
end.check_request do |_endpoint, data, _headers|
assert_match(/012<\/paymentSolution>/, data)
end.respond_with(successful_purchase_response)
@@ -299,7 +312,7 @@ def test_authorize_includes_customer_id
def test_authorize_with_apple_pay_includes_payment_solution_001
stub_comms do
- @gateway.authorize(100, network_tokenization_credit_card('4242424242424242', source: :apple_pay))
+ @gateway.authorize(100, @apple_pay)
end.check_request do |_endpoint, data, _headers|
assert_match(/001<\/paymentSolution>/, data)
end.respond_with(successful_authorization_response)
@@ -307,7 +320,7 @@ def test_authorize_with_apple_pay_includes_payment_solution_001
def test_authorize_with_google_pay_includes_payment_solution_012
stub_comms do
- @gateway.authorize(100, network_tokenization_credit_card('4242424242424242', source: :google_pay))
+ @gateway.authorize(100, @google_pay)
end.check_request do |_endpoint, data, _headers|
assert_match(/012<\/paymentSolution>/, data)
end.respond_with(successful_authorization_response)
@@ -385,15 +398,8 @@ def test_successful_network_token_purchase_single_request_ignore_avs
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram'
- )
options = @options.merge(ignore_avs: true)
- assert response = @gateway.purchase(@amount, credit_card, options)
+ assert response = @gateway.purchase(@amount, @network_token, options)
assert_success response
end
@@ -429,9 +435,7 @@ def test_successful_credit_cart_purchase_single_request_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: true
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: true))
assert_success response
end
@@ -442,15 +446,8 @@ def test_successful_network_token_purchase_single_request_ignore_cvv
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram'
- )
options = @options.merge(ignore_cvv: true)
- assert response = @gateway.purchase(@amount, credit_card, options)
+ assert response = @gateway.purchase(@amount, @network_token, options)
assert_success response
end
@@ -461,9 +458,7 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: false
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: false))
assert_success response
@gateway.expects(:ssl_post).with do |_host, request_body|
@@ -472,9 +467,69 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: 'false'
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: 'false'))
+ assert_success response
+ end
+
+ def test_successful_apple_pay_purchase_subsequent_auth_visa
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_not_match %r'', request_body
+ assert_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, @apple_pay, options)
+ assert_success response
+ end
+
+ def test_successful_apple_pay_purchase_subsequent_auth_mastercard
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :apple_pay)
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_network_token_purchase_subsequent_auth_visa
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_match %r'111111111100cryptogram', request_body
+ assert_match %r'vbv', request_body
+ assert_not_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, @network_token, options)
assert_success response
end
@@ -885,16 +940,8 @@ def test_unsuccessful_verify
end
def test_successful_auth_with_network_tokenization_for_visa
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram'
- )
-
response = stub_comms do
- @gateway.authorize(@amount, credit_card, @options)
+ @gateway.authorize(@amount, @network_token, @options)
end.check_request do |_endpoint, body, _headers|
assert_xml_valid_to_xsd(body)
assert_match %r'\n 111111111100cryptogram\n vbv\n 111111111100cryptogram\n\n\n\n\n 1\n', body
@@ -904,16 +951,8 @@ def test_successful_auth_with_network_tokenization_for_visa
end
def test_successful_purchase_with_network_tokenization_for_visa
- credit_card = network_tokenization_credit_card(
- '4111111111111111',
- brand: 'visa',
- transaction_id: '123',
- eci: '05',
- payment_cryptogram: '111111111100cryptogram'
- )
-
response = stub_comms do
- @gateway.purchase(@amount, credit_card, @options)
+ @gateway.purchase(@amount, @network_token, @options)
end.check_request do |_endpoint, body, _headers|
assert_xml_valid_to_xsd(body)
assert_match %r'.+?'m, body
From 2eeb3ab21ef3e415c16bcb9b8f74280e6ac1ccf8 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 6 Sep 2023 09:46:49 -0500
Subject: [PATCH 156/390] SER-728 Create Verve Card Type. This change new
credit card brands verve and tests related to it. (#4875)
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
lib/active_merchant/billing/credit_card.rb | 2 +
.../billing/credit_card_methods.rb | 40 ++++++++++++++++++-
lib/active_merchant/billing/gateways/rapyd.rb | 2 +-
test/unit/credit_card_methods_test.rb | 33 +++++++++++++++
5 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 33d7e9d4282..1754b39cf45 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
* TNS: Use the specified order_id in request if available [yunnydang] #4880
* Cybersource: Support recurring apple pay [aenand] #4874
+* Verve BIN ranges and add card type to Rapyd gateway [jherreraa] #4875
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb
index 8ad539c8a02..6982139dad3 100644
--- a/lib/active_merchant/billing/credit_card.rb
+++ b/lib/active_merchant/billing/credit_card.rb
@@ -39,6 +39,7 @@ module Billing #:nodoc:
# * Anda
# * Creditos directos (Tarjeta D)
# * Panal
+ # * Verve
#
# For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
# validations, allowing you to focus on your core concerns until you're ready to be more concerned
@@ -132,6 +133,7 @@ def number=(value)
# * +'anda'+
# * +'tarjeta-d'+
# * +'panal'+
+ # * +'verve'+
#
# Or, if you wish to test your implementation, +'bogus'+.
#
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 4f11160c7fc..29992b6d617 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -47,7 +47,8 @@ module CreditCardMethods
'anda' => ->(num) { num =~ /^603199\d{10}$/ },
'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
- 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) }
+ 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) },
+ 'verve' => ->(num) { (16..19).cover?(num&.size) && in_bin_range?(num.slice(0, 6), VERVE_RANGES) }
}
SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -251,6 +252,43 @@ module CreditCardMethods
PANAL_RANGES = [[602049]]
+ VERVE_RANGES = [
+ [506099],
+ [506101],
+ [506103],
+ (506111..506114),
+ [506116],
+ [506118],
+ [506124],
+ [506127],
+ [506130],
+ (506132..506139),
+ [506141],
+ [506144],
+ (506146..506152),
+ (506154..506161),
+ (506163..506164),
+ [506167],
+ (506169..506198),
+ (507865..507866),
+ (507868..507872),
+ (507874..507899),
+ (507901..507909),
+ (507911..507919),
+ [507921],
+ (507923..507925),
+ (507927..507962),
+ [507964],
+ [627309],
+ [627903],
+ [628051],
+ [636625],
+ [637058],
+ [637634],
+ [639245],
+ [639383]
+ ]
+
def self.included(base)
base.extend(ClassMethods)
end
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 637f7250af4..d566a84fce8 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -6,7 +6,7 @@ class RapydGateway < Gateway
self.supported_countries = %w(CA CL CO DO SV PE PT VI AU HK IN ID JP MY NZ PH SG KR TW TH VN AD AT BE BA BG HR CY CZ DK EE FI FR GE DE GI GR GL HU IS IE IL IT LV LI LT LU MK MT MD MC ME NL GB NO PL RO RU SM SK SI ZA ES SE CH TR VA)
self.default_currency = 'USD'
- self.supported_cardtypes = %i[visa master american_express discover]
+ self.supported_cardtypes = %i[visa master american_express discover verve]
self.homepage_url = 'https://www.rapyd.net/'
self.display_name = 'Rapyd Gateway'
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index ce8447abc11..9ff10d72eb0 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -506,6 +506,39 @@ def test_should_detect_panal_card
assert_equal 'panal', CreditCard.brand?('6020490000000000')
end
+ def test_detecting_full_range_of_verve_card_numbers
+ verve = '506099000000000'
+
+ assert_equal 15, verve.length
+ assert_not_equal 'verve', CreditCard.brand?(verve)
+
+ 4.times do
+ verve << '0'
+ assert_equal 'verve', CreditCard.brand?(verve), "Failed for bin #{verve}"
+ end
+
+ assert_equal 19, verve.length
+
+ verve << '0'
+ assert_not_equal 'verve', CreditCard.brand?(verve)
+ end
+
+ def test_should_detect_verve
+ credit_cards = %w[5060990000000000
+ 506112100000000000
+ 5061351000000000000
+ 5061591000000000
+ 506175100000000000
+ 5078801000000000000
+ 5079381000000000
+ 637058100000000000
+ 5079400000000000000
+ 507879000000000000
+ 5061930000000000
+ 506136000000000000]
+ credit_cards.all? { |cc| CreditCard.brand?(cc) == 'verve' }
+ end
+
def test_credit_card?
assert credit_card.credit_card?
end
From f2b2fcb50f877ead3d49d1a584f4937b54245e9b Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 28 Aug 2023 19:14:05 -0700
Subject: [PATCH 157/390] Rapyd: Add fields and update stored credential method
Local: 5588 tests, 77761 assertions, 0 failures, 19 errors, 0 pendings, 0
omissions, 0 notifications 99.66% passed
Unit:
27 tests, 137 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
34 tests, 95 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 18 +++++++++--
test/remote/gateways/remote_rapyd_test.rb | 31 ++++++++++++++++---
test/unit/gateways/rapyd_test.rb | 13 ++++++++
4 files changed, 55 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 1754b39cf45..ffcfd2346ee 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@
* TNS: Use the specified order_id in request if available [yunnydang] #4880
* Cybersource: Support recurring apple pay [aenand] #4874
* Verve BIN ranges and add card type to Rapyd gateway [jherreraa] #4875
+* Rapyd: Add network_reference_id, initiation_type, and update stored credential method [yunnydang] #4877
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index d566a84fce8..c7349907ddf 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -141,10 +141,22 @@ def add_payment(post, payment, options)
end
def add_stored_credential(post, options)
- return unless stored_credential = options[:stored_credential]
+ add_network_reference_id(post, options)
+ add_initiation_type(post, options)
+ end
+
+ def add_network_reference_id(post, options)
+ return unless options[:stored_credential] || options[:network_transaction_id]
+
+ network_transaction_id = options[:network_transaction_id] || options[:stored_credential][:network_transaction_id]
+ post[:payment_method][:fields][:network_reference_id] = network_transaction_id if network_transaction_id
+ end
+
+ def add_initiation_type(post, options)
+ return unless options[:stored_credential] || options[:initiation_type]
- post[:payment_method][:fields][:network_reference_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
- post[:initiation_type] = stored_credential[:reason_type] if stored_credential[:reason_type]
+ initiation_type = options[:initiation_type] || options[:stored_credential][:reason_type]
+ post[:initiation_type] = initiation_type if initiation_type
end
def add_creditcard(post, payment, options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 8be0c3002af..8d1ddc9668a 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -19,6 +19,17 @@ def setup
billing_address: address(name: 'Jim Reynolds'),
order_id: '987654321'
}
+ @stored_credential_options = {
+ pm_type: 'gb_visa_card',
+ currency: 'GBP',
+ complete_payment_url: 'https://www.rapyd.net/platform/collect/online/',
+ error_payment_url: 'https://www.rapyd.net/platform/collect/online/',
+ description: 'Describe this transaction',
+ statement_descriptor: 'Statement Descriptor',
+ email: 'test@example.com',
+ billing_address: address(name: 'Jim Reynolds'),
+ order_id: '987654321'
+ }
@ach_options = {
pm_type: 'us_ach_bank',
currency: 'USD',
@@ -81,15 +92,25 @@ def test_success_purchase_without_address_object_customer
end
def test_successful_subsequent_purchase_with_stored_credential
- @options[:currency] = 'GBP'
- @options[:pm_type] = 'gb_visa_card'
- @options[:complete_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
- @options[:error_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: rand.to_s[2..11], initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields_along_with_stored_credentials
# Rapyd requires a random int between 10 and 15 digits for NTID
- response = @gateway.purchase(15000, @credit_card, @options.merge({ stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' } }))
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }, network_transaction_id: rand.to_s[2..11], initiation_type: 'customer_present'))
assert_success response
assert_equal 'SUCCESS', response.message
+ assert_equal 'customer_present', response.params['data']['initiation_type']
end
def test_successful_purchase_with_address
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 03070aa1b33..9b6d63a8600 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -122,6 +122,19 @@ def test_successful_purchase_with_stored_credential
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields
+ @options[:network_transaction_id] = '54321'
+ @options[:initiation_type] = 'customer_present'
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['payment_method']['fields']['network_reference_id'], @options[:network_transaction_id]
+ assert_equal request['initiation_type'], @options[:initiation_type]
+ end.respond_with(successful_purchase_response)
+ end
+
def test_successful_purchase_with_3ds_gateway_specific
@options[:three_d_secure] = {
required: true,
From 3e89d925b8d6baab43a474509d949c360512b097 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Fri, 8 Sep 2023 11:05:22 -0500
Subject: [PATCH 158/390] CommerceHub: Update headers (#4853)
Description
-------------------------
This commit add new headers identifiers: 'x-originator', 'user-agent'.
Additionally include a small test to verify the headers presence.
[SER-621](https://spreedly.atlassian.net/browse/SER-621)
Unit test
-------------------------
Finished in 0.033772 seconds.
25 tests, 175 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
740.26 tests/s, 5181.81 assertions/s
Remote test
-------------------------
Finished in 57.0542 seconds.
26 tests, 62 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
80.7692% passed
0.46 tests/s, 1.09 assertions/s
Rubocop
-------------------------
763 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
.../billing/gateways/commerce_hub.rb | 4 ++--
test/unit/gateways/commerce_hub_test.rb | 24 +++++++++++++++++++
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 669b560445b..e4d9acca748 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -282,7 +282,7 @@ def headers(request, options)
raw_signature = @options[:api_key] + client_request_id.to_s + time + request
hmac = OpenSSL::HMAC.digest('sha256', @options[:api_secret], raw_signature)
signature = Base64.strict_encode64(hmac.to_s).to_s
-
+ custom_headers = options.fetch(:headers_identifiers, {})
{
'Client-Request-Id' => client_request_id,
'Api-Key' => @options[:api_key],
@@ -292,7 +292,7 @@ def headers(request, options)
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => signature
- }
+ }.merge!(custom_headers)
end
def add_merchant_details(post)
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index cf6af56b989..b2b085be356 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -42,6 +42,30 @@ def setup
@post = {}
end
+ def test_successful_authorize_with_full_headers
+ @options.merge!(
+ headers_identifiers: {
+ 'x-originator' => 'CommerceHub-Partners-Spreedly',
+ 'user-agent' => 'CommerceHub-Partners-Spreedly-V1.00'
+ }
+ )
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, _data, headers|
+ assert_not_nil headers['Client-Request-Id']
+ assert_equal 'login', headers['Api-Key']
+ assert_not_nil headers['Timestamp']
+ assert_equal 'application/json', headers['Accept-Language']
+ assert_equal 'application/json', headers['Content-Type']
+ assert_equal 'application/json', headers['Accept']
+ assert_equal 'HMAC', headers['Auth-Token-Type']
+ assert_not_nil headers['Authorization']
+ assert_equal 'CommerceHub-Partners-Spreedly', headers['x-originator']
+ assert_equal 'CommerceHub-Partners-Spreedly-V1.00', headers['user-agent']
+ end.respond_with(successful_authorize_response)
+ end
+
def test_successful_purchase
@options[:order_id] = 'abc123'
From 0013c6f29e5ad37b298643ff1ae82d2a56a70d60 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Tue, 29 Aug 2023 18:19:42 -0700
Subject: [PATCH 159/390] Adyen: Add the store field
Local:
5589 tests, 77761 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications
99.66% passed
Unit:
111 tests, 583 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
134 tests, 447 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.791% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 1 +
test/unit/gateways/adyen_test.rb | 9 +++++++++
3 files changed, 11 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index ffcfd2346ee..0157fa5694c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,7 @@
* Cybersource: Support recurring apple pay [aenand] #4874
* Verve BIN ranges and add card type to Rapyd gateway [jherreraa] #4875
* Rapyd: Add network_reference_id, initiation_type, and update stored credential method [yunnydang] #4877
+* Adyen: Add the store field [yunnydang] #4878
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 0d93164e94f..6bb97a036f3 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -245,6 +245,7 @@ def add_extra_data(post, payment, options)
post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
+ post[:store] = options[:store] if options[:store]
add_shopper_data(post, options)
add_risk_data(post, options)
add_shopper_reference(post, options)
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 18a4a48a0e4..7978bbacba0 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -1435,6 +1435,15 @@ def test_additional_data_lodging
assert_success response
end
+ def test_additional_extra_data
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(store: 'test store'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal JSON.parse(data)['store'], 'test store'
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_extended_avs_response
response = stub_comms do
@gateway.verify(@credit_card, @options)
From d185244ab33a3f2329e54d549ec620b5ddf61961 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Thu, 7 Sep 2023 17:20:32 -0700
Subject: [PATCH 160/390] Stripe PI: Expand balance txns for off session
transactions
---
CHANGELOG | 1 +
.../billing/gateways/stripe_payment_intents.rb | 1 +
.../remote/gateways/remote_stripe_payment_intents_test.rb | 3 ++-
test/unit/gateways/stripe_payment_intents_test.rb | 8 ++++++++
4 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0157fa5694c..baece195697 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,7 @@
* Verve BIN ranges and add card type to Rapyd gateway [jherreraa] #4875
* Rapyd: Add network_reference_id, initiation_type, and update stored credential method [yunnydang] #4877
* Adyen: Add the store field [yunnydang] #4878
+* Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 4d281a77151..5a0daeec936 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -43,6 +43,7 @@ def create_intent(money, payment_method, options = {})
add_fulfillment_date(post, options)
request_three_d_secure(post, options)
add_level_three(post, options)
+ post[:expand] = ['charges.data.balance_transaction']
CREATE_INTENT_ATTRIBUTES.each do |attribute|
add_whitelisted_attribute(post, options, attribute)
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index c2957770232..e0277f47122 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -95,9 +95,10 @@ def test_successful_purchase
customer: @customer
}
assert purchase = @gateway.purchase(@amount, @visa_payment_method, options)
-
assert_equal 'succeeded', purchase.params['status']
+
assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['balance_transaction']
end
def test_successful_purchase_with_shipping_address
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index df3abed1f71..320c7d52b4a 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -464,6 +464,14 @@ def test_sends_network_transaction_id_separate_from_stored_creds
end.respond_with(successful_create_intent_response)
end
+ def test_sends_expand_balance_transaction
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('expand[0]=charges.data.balance_transaction', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
def test_purchase_with_google_pay
options = {
currency: 'GBP'
From fdf8d37e70cca35e7bffbb60aa0997f85485df8e Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Tue, 12 Sep 2023 08:06:20 -0500
Subject: [PATCH 161/390] Rapyd: Update cvv behavior (#4883)
Description
-------------------------
[SER-808](https://spreedly.atlassian.net/browse/SER-808)
This commit validate if the cvv in order to don't allow send empty values to Rapyd
This commit also include a small update for the previos work made for
[3ds Gateway Specific](https://github.com/activemerchant/active_merchant/pull/4876)
Unit test
-------------------------
Finished in 0.183111 seconds.
29 tests, 148 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notificationsomissions, 0 notifications
100% passed
158.37 tests/s, 808.25 assertions/s
Remote test
-------------------------
Finished in 113.329864 seconds.
34 tests, 111 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 97.0588% passed
0.30 tests/s, 0.86 assertions/s
Rubocop
-------------------------
763 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
lib/active_merchant/billing/gateways/rapyd.rb | 22 ++++++++++---------
test/remote/gateways/remote_rapyd_test.rb | 17 ++++++++++++++
test/unit/gateways/rapyd_test.rb | 11 ++++++++++
3 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index c7349907ddf..29e395b4285 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -169,7 +169,7 @@ def add_creditcard(post, payment, options)
pm_fields[:expiration_month] = payment.month.to_s
pm_fields[:expiration_year] = payment.year.to_s
pm_fields[:name] = "#{payment.first_name} #{payment.last_name}"
- pm_fields[:cvv] = payment.verification_value.to_s
+ pm_fields[:cvv] = payment.verification_value.to_s if payment.verification_value.present?
add_stored_credential(post, options)
end
@@ -197,15 +197,17 @@ def add_tokens(post, payment, options)
end
def add_3ds(post, payment, options)
- return unless three_d_secure = options[:three_d_secure]
-
- post[:payment_method_options] = {}
- post[:payment_method_options]['3d_required'] = three_d_secure[:required]
- post[:payment_method_options]['3d_version'] = three_d_secure[:version]
- post[:payment_method_options][:cavv] = three_d_secure[:cavv]
- post[:payment_method_options][:eci] = three_d_secure[:eci]
- post[:payment_method_options][:xid] = three_d_secure[:xid]
- post[:payment_method_options][:ds_trans_id] = three_d_secure[:ds_transaction_id]
+ if options[:execute_threed] == true
+ post[:payment_method_options] = { '3d_required' => true }
+ elsif three_d_secure = options[:three_d_secure]
+ post[:payment_method_options] = {}
+ post[:payment_method_options]['3d_required'] = three_d_secure[:required]
+ post[:payment_method_options]['3d_version'] = three_d_secure[:version]
+ post[:payment_method_options][:cavv] = three_d_secure[:cavv]
+ post[:payment_method_options][:eci] = three_d_secure[:eci]
+ post[:payment_method_options][:xid] = three_d_secure[:xid]
+ post[:payment_method_options][:ds_trans_id] = three_d_secure[:ds_transaction_id]
+ end
end
def add_metadata(post, options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 8d1ddc9668a..18389ad05cb 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -366,4 +366,21 @@ def test_successful_purchase_without_3ds_v2_gateway_specific
assert_equal 'not_applicable', response.params['data']['payment_method_data']['next_action']
assert_equal '', response.params['data']['redirect_url']
end
+
+ def test_successful_authorize_with_execute_threed
+ options = @options.merge(pm_type: 'gb_visa_card', execute_threed: true)
+ response = @gateway.authorize(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ assert response.params['data']['redirect_url']
+ end
+
+ def test_successful_purchase_without_cvv
+ options = @options.merge({ pm_type: 'gb_visa_card', stored_credential: { network_transaction_id: rand.to_s[2..11] } })
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(100, @credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 9b6d63a8600..4bed0c08f73 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -55,6 +55,17 @@ def test_successful_purchase
assert_equal 'payment_716ce0efc63aa8d91579e873d29d9d5e', response.authorization.split('|')[0]
end
+ def test_successful_purchase_without_cvv
+ @credit_card.verification_value = nil
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"number":"4242424242424242","expiration_month":"9","expiration_year":"2024","name":"Longbob Longsen/, data)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ assert_equal 'payment_716ce0efc63aa8d91579e873d29d9d5e', response.authorization.split('|')[0]
+ end
+
def test_successful_purchase_with_ach
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @check, @options.merge(billing_address: address(name: 'Joe John-ston')))
From 815dcbca69d3ccd8708c09c1d5c8ae0a04335622 Mon Sep 17 00:00:00 2001
From: Britney Smith
Date: Wed, 6 Sep 2023 15:50:00 -0400
Subject: [PATCH 162/390] CyberSource (SOAP): Added support for 3DS exemption
request fields
Test Summary
Local: 5591 tests, 77934 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit at test/unit/gateways/cyber_source_test.rb: 138 tests, 762 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote at test/remote/gateways/remote_cyber_source_test.rb: 125 tests, 627 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96% passed (same as `master`)
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 35 ++++++++-
.../gateways/remote_cyber_source_test.rb | 66 ++++++++++++++++
test/unit/gateways/cyber_source_test.rb | 76 +++++++++++++++++++
4 files changed, 177 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index baece195697..db808176284 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
* Rapyd: Add network_reference_id, initiation_type, and update stored credential method [yunnydang] #4877
* Adyen: Add the store field [yunnydang] #4878
* Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882
+* CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 387b89863be..d9a6cc996e1 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -33,6 +33,15 @@ class CyberSourceGateway < Gateway
discover: 'pb',
diners_club: 'pb'
}.freeze
+ THREEDS_EXEMPTIONS = {
+ authentication_outage: 'authenticationOutageExemptionIndicator',
+ corporate_card: 'secureCorporatePaymentIndicator',
+ delegated_authentication: 'delegatedAuthenticationExemptionIndicator',
+ low_risk: 'riskAnalysisExemptionIndicator',
+ low_value: 'lowValueExemptionIndicator',
+ stored_credential: 'stored_credential',
+ trusted_merchant: 'trustedMerchantExemptionIndicator'
+ }
DEFAULT_COLLECTION_INDICATOR = 2
self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro elo]
@@ -736,6 +745,7 @@ def add_auth_service(xml, payment_method, options)
xml.tag! 'ccAuthService', { 'run' => 'true' } do
if options[:three_d_secure]
add_normalized_threeds_2_data(xml, payment_method, options)
+ add_threeds_exemption_data(xml, options) if options[:three_ds_exemption_type]
else
indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
xml.tag!('commerceIndicator', indicator) if indicator
@@ -746,6 +756,17 @@ def add_auth_service(xml, payment_method, options)
end
end
+ def add_threeds_exemption_data(xml, options)
+ return unless options[:three_ds_exemption_type]
+
+ exemption = options[:three_ds_exemption_type].to_sym
+
+ case exemption
+ when :authentication_outage, :corporate_card, :delegated_authentication, :low_risk, :low_value, :trusted_merchant
+ xml.tag!(THREEDS_EXEMPTIONS[exemption], '1')
+ end
+ end
+
def add_incremental_auth_service(xml, authorization, options)
xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
xml.tag! 'authRequestID', authorization
@@ -1014,7 +1035,7 @@ def add_stored_credential_options(xml, options = {})
stored_credential_subsequent_auth_first = 'true' if options.dig(:stored_credential, :initial_transaction)
stored_credential_transaction_id = options.dig(:stored_credential, :network_transaction_id) if options.dig(:stored_credential, :initiator) == 'merchant'
- stored_credential_subsequent_auth_stored_cred = 'true' if options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction) || options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ stored_credential_subsequent_auth_stored_cred = 'true' if subsequent_cardholder_initiated_transaction?(options) || unscheduled_merchant_initiated_transaction?(options) || threeds_stored_credential_exemption?(options)
override_subsequent_auth_first = options.dig(:stored_credential_overrides, :subsequent_auth_first)
override_subsequent_auth_transaction_id = options.dig(:stored_credential_overrides, :subsequent_auth_transaction_id)
@@ -1025,6 +1046,18 @@ def add_stored_credential_options(xml, options = {})
xml.subsequentAuthStoredCredential override_subsequent_auth_stored_cred.nil? ? stored_credential_subsequent_auth_stored_cred : override_subsequent_auth_stored_cred
end
+ def subsequent_cardholder_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction)
+ end
+
+ def unscheduled_merchant_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ end
+
+ def threeds_stored_credential_exemption?(options)
+ options[:three_ds_exemption_type] == THREEDS_EXEMPTIONS[:stored_credential]
+ end
+
def add_partner_solution_id(xml)
return unless application_id
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 14d9ca65e31..7f235ad910a 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -1190,6 +1190,72 @@ def test_successful_subsequent_unscheduled_cof_purchase
assert_successful_response(response)
end
+ def test_successful_authorize_with_3ds_exemption
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: 'moto'))
+ assert_successful_response(response)
+ end
+
+ def test_successful_purchase_with_3ds_exemption
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.purchase(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: 'moto'))
+ assert_successful_response(response)
+ end
+
+ def test_successful_recurring_cof_authorize_with_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: ''
+ }
+
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential]))
+ assert_successful_response(response)
+ end
+
+ def test_successful_recurring_cof_purchase_with_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: ''
+ }
+
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.purchase(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential]))
+ assert_successful_response(response)
+ end
+
def test_invalid_field
@options = @options.merge({
address: {
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 9efca5a9cc9..0e67c44700c 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -1420,6 +1420,82 @@ def test_does_not_add_cavv_as_xid_if_xid_is_present
end.respond_with(successful_purchase_response)
end
+ def test_add_3ds_exemption_fields_except_stored_credential
+ CyberSourceGateway::THREEDS_EXEMPTIONS.keys.reject { |k| k == :stored_credential }.each do |exemption|
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options_with_normalized_3ds, three_ds_exemption_type: exemption.to_s, merchant_id: 'test', billing_address: {
+ 'address1' => '221B Baker Street',
+ 'city' => 'London',
+ 'zip' => 'NW16XE',
+ 'country' => 'GB'
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ # billing details
+ assert_match(%r(\n), data)
+ assert_match(%r(Longbob), data)
+ assert_match(%r(Longsen), data)
+ assert_match(%r(221B Baker Street), data)
+ assert_match(%r(London), data)
+ assert_match(%r(NW16XE), data)
+ assert_match(%r(GB), data)
+ # card details
+ assert_match(%r(\n), data)
+ assert_match(%r(4111111111111111), data)
+ assert_match(%r(#{@gateway.format(@credit_card.month, :two_digits)}), data)
+ assert_match(%r(#{@gateway.format(@credit_card.year, :four_digits)}), data)
+ # merchant data
+ assert_match(%r(test), data)
+ assert_match(%r(#{@options[:order_id]}), data)
+ # amount data
+ assert_match(%r(\n), data)
+ assert_match(%r(#{@gateway.send(:localized_amount, @amount.to_i, @options[:currency])}), data)
+ # 3ds exemption tag
+ assert_match %r(\n), data
+ assert_match(%r(<#{CyberSourceGateway::THREEDS_EXEMPTIONS[exemption]}>1#{CyberSourceGateway::THREEDS_EXEMPTIONS[exemption]}>), data)
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
+ def test_add_stored_credential_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options_with_normalized_3ds, three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential], merchant_id: 'test', billing_address: {
+ 'address1' => '221B Baker Street',
+ 'city' => 'London',
+ 'zip' => 'NW16XE',
+ 'country' => 'GB'
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ # billing details
+ assert_match(%r(\n), data)
+ assert_match(%r(Longbob), data)
+ assert_match(%r(Longsen), data)
+ assert_match(%r(221B Baker Street), data)
+ assert_match(%r(London), data)
+ assert_match(%r(NW16XE), data)
+ assert_match(%r(GB), data)
+ # card details
+ assert_match(%r(\n), data)
+ assert_match(%r(4111111111111111), data)
+ assert_match(%r(#{@gateway.format(@credit_card.month, :two_digits)}), data)
+ assert_match(%r(#{@gateway.format(@credit_card.year, :four_digits)}), data)
+ # merchant data
+ assert_match(%r(test), data)
+ assert_match(%r(#{@options[:order_id]}), data)
+ # amount data
+ assert_match(%r(\n), data)
+ assert_match(%r(#{@gateway.send(:localized_amount, @amount.to_i, @options[:currency])}), data)
+ # 3ds exemption tag
+ assert_match(%r(true), data)
+ end.respond_with(successful_purchase_response)
+ end
+
def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
From 3b28d8622f26121b0eff29d4f0b6bc9be6c817cd Mon Sep 17 00:00:00 2001
From: Britney Smith
Date: Tue, 11 Jul 2023 16:03:16 -0400
Subject: [PATCH 163/390] StripePI: Adding network tokenization fields to
Stripe PaymentIntents
The `last4` field is the only new `option`, and is added upstream. This work includes fields for one-time use PaymentIntents only.
Test Summary
Local: 5582 tests, 77796 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit at test/unit/gateways/stripe_payment_intents_test.rb: 55 tests, 289 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote at test/remote/gateways/remote_stripe_payment_intents_test.rb: 90 tests, 424 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 49 +-
.../remote_stripe_payment_intents_test.rb | 41 ++
.../gateways/stripe_payment_intents_test.rb | 575 ++++++++++++++++++
4 files changed, 662 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index db808176284..c1d5da622d7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@
* Adyen: Add the store field [yunnydang] #4878
* Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882
* CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881
+* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 5a0daeec936..f423ff39aa5 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -14,7 +14,7 @@ class StripePaymentIntentsGateway < StripeGateway
def create_intent(money, payment_method, options = {})
MultiResponse.run do |r|
- if payment_method.is_a?(NetworkTokenizationCreditCard)
+ if payment_method.is_a?(NetworkTokenizationCreditCard) && digital_wallet_payment_method?(payment_method)
r.process { tokenize_apple_google(payment_method, options) }
payment_method = (r.params['token']['id']) if r.success?
end
@@ -28,6 +28,7 @@ def create_intent(money, payment_method, options = {})
result = add_payment_method_token(post, payment_method, options)
return result if result.is_a?(ActiveMerchant::Billing::Response)
+ add_network_token_cryptogram_and_eci(post, payment_method)
add_external_three_d_secure_auth_data(post, options)
add_metadata(post, options)
add_return_url(post, options)
@@ -85,18 +86,18 @@ def add_payment_method_data(payment_method, options = {})
post = {
type: 'card',
card: {
- number: payment_method.number,
exp_month: payment_method.month,
exp_year: payment_method.year
}
}
-
+ post[:card][:number] = payment_method.number unless adding_network_token_card_data?(payment_method)
post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
if billing = options[:billing_address] || options[:address]
post[:billing_details] = add_address(billing, options)
end
add_name_only(post, payment_method) if post[:billing_details].nil?
+ add_network_token_data(post, payment_method, options)
post
end
@@ -268,8 +269,22 @@ def setup_purchase(money, options = {})
commit(:post, 'payment_intents', post, options)
end
+ def supports_network_tokenization?
+ true
+ end
+
private
+ def digital_wallet_payment_method?(payment_method)
+ payment_method.source == :google_pay || payment_method.source == :apple_pay
+ end
+
+ def adding_network_token_card_data?(payment_method)
+ return true if payment_method.is_a?(ActiveMerchant::Billing::NetworkTokenizationCreditCard) && payment_method.source == :network_token
+
+ false
+ end
+
def off_session_request?(options = {})
(options[:off_session] || options[:setup_future_usage]) && options[:confirm] == true
end
@@ -343,10 +358,36 @@ def add_payment_method_token(post, payment_method, options, responses = [])
when ActiveMerchant::Billing::CreditCard
return create_payment_method_and_extract_token(post, payment_method, options, responses) if options[:verify]
+ get_payment_method_data_from_card(post, payment_method, options, responses)
+ when ActiveMerchant::Billing::NetworkTokenizationCreditCard
get_payment_method_data_from_card(post, payment_method, options, responses)
end
end
+ def add_network_token_data(post_data, payment_method, options)
+ return unless adding_network_token_card_data?(payment_method)
+
+ post_data[:card] ||= {}
+ post_data[:card][:last4] = options[:last_4]
+ post_data[:card][:network_token] = {}
+ post_data[:card][:network_token][:number] = payment_method.number
+ post_data[:card][:network_token][:exp_month] = payment_method.month
+ post_data[:card][:network_token][:exp_year] = payment_method.year
+ post_data[:card][:network_token][:payment_account_reference] = options[:payment_account_reference] if options[:payment_account_reference]
+
+ post_data
+ end
+
+ def add_network_token_cryptogram_and_eci(post, payment_method)
+ return unless adding_network_token_card_data?(payment_method)
+
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:network_token] ||= {}
+ post[:payment_method_options][:card][:network_token][:cryptogram] = payment_method.payment_cryptogram if payment_method.payment_cryptogram
+ post[:payment_method_options][:card][:network_token][:electronic_commerce_indicator] = payment_method.eci if payment_method.eci
+ end
+
def extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
if payment_method.include?('|')
customer_id, payment_method = payment_method.split('|')
@@ -385,7 +426,7 @@ def tokenize_apple_google(payment, options = {})
end
def get_payment_method_data_from_card(post, payment_method, options, responses)
- return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options)
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options) || adding_network_token_card_data?(payment_method)
post[:payment_method_data] = add_payment_method_data(payment_method, options)
end
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index e0277f47122..14811db6d95 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -72,6 +72,17 @@ def setup
last_name: 'Longsen'
)
+ @network_token_credit_card = network_tokenization_credit_card(
+ '4000056655665556',
+ payment_cryptogram: 'AAEBAwQjSQAAXXXXXXXJYe0BbQA=',
+ source: :network_token,
+ brand: 'visa',
+ month: '09',
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
+ )
+
@destination_account = fixtures(:stripe_destination)[:stripe_user_id]
end
@@ -212,6 +223,18 @@ def test_successful_purchase_with_google_pay
assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
end
+ def test_successful_purchase_with_tokenized_visa
+ options = {
+ currency: 'USD',
+ last_4: '4242'
+ }
+
+ purchase = @gateway.purchase(@amount, @network_token_credit_card, options)
+ assert_equal(nil, purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ assert purchase.success?
+ assert_not_nil(purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_token'])
+ end
+
def test_successful_purchase_with_google_pay_when_sending_the_billing_address
options = {
currency: 'GBP',
@@ -1029,6 +1052,24 @@ def test_create_a_payment_intent_and_manually_capture
assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
end
+ def test_create_a_payment_intent_and_manually_capture_with_network_token
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ confirm: true,
+ last_4: '4242'
+ }
+ assert create_response = @gateway.create_intent(@amount, @network_token_credit_card, options)
+ intent_id = create_response.params['id']
+ assert_equal 'requires_capture', create_response.params['status']
+
+ assert capture_response = @gateway.capture(@amount, intent_id, options)
+ assert_equal 'succeeded', capture_response.params['status']
+ assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
+ end
+
def test_failed_create_a_payment_intent_with_set_error_on_requires_action
options = {
currency: 'GBP',
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 320c7d52b4a..175b6d16211 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -47,6 +47,18 @@ def setup
first_name: 'Longbob',
last_name: 'Longsen'
)
+
+ @network_token_credit_card = network_tokenization_credit_card(
+ '4000056655665556',
+ verification_value: '123',
+ payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
+ source: :network_token,
+ brand: 'visa',
+ month: '09',
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
+ )
end
def test_successful_create_and_confirm_intent
@@ -78,6 +90,19 @@ def test_successful_create_and_capture_intent
assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
end
+ def test_successful_create_and_capture_intent_with_network_token
+ options = @options.merge(capture_method: 'manual', confirm: true)
+ @gateway.expects(:ssl_request).twice.returns(successful_create_intent_manual_capture_response_with_network_token_fields, successful_manual_capture_of_payment_intent_response_with_network_token_fields)
+ assert create = @gateway.create_intent(@amount, @network_token_credit_card, options)
+ assert_success create
+ assert_equal 'requires_capture', create.params['status']
+
+ assert capture = @gateway.capture(@amount, create.params['id'], options)
+ assert_success capture
+ assert_equal 'succeeded', capture.params['status']
+ assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
+ end
+
def test_successful_create_and_update_intent
@gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_update_intent_response)
assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual'))
@@ -502,6 +527,28 @@ def test_purchase_with_google_pay_with_billing_address
end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address)
end
+ def test_purchase_with_network_token_card
+ options = {
+ currency: 'USD',
+ last_4: '4242'
+ }
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @network_token_credit_card, options)
+ end.check_request do |_method, endpoint, data, _headers|
+ assert_match(%r{/payment_intents}, endpoint)
+ assert_match('confirm=true', data)
+ assert_match('payment_method_data[type]=card', data)
+ assert_match('[card][exp_month]=9', data)
+ assert_match('[card][exp_year]=2030', data)
+ assert_match('[card][last4]=4242', data)
+ assert_match('[card][network_token][number]=4000056655665556', data)
+ assert_match("[card][network_token][cryptogram]=#{URI.encode_www_form_component('dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==')}", data)
+ assert_match('[card][network_token][exp_month]=9', data)
+ assert_match('[card][network_token][exp_year]=2030', data)
+ end.respond_with(successful_create_intent_response_with_network_token_fields)
+ end
+
def test_purchase_with_shipping_options
options = {
currency: 'GBP',
@@ -912,6 +959,534 @@ def successful_create_intent_response
RESPONSE
end
+ def successful_create_intent_response_with_network_token_fields
+ <<~RESPONSE
+ {
+ "id": "pi_3NfRruAWOtgoysog1FxgDwtf",
+ "object": "payment_intent",
+ "amount": 2000,
+ "amount_capturable": 0,
+ "amount_details": {
+ "tip": {
+ }
+ },
+ "amount_received": 2000,
+ "application": null,
+ "application_fee_amount": null,
+ "automatic_payment_methods": null,
+ "canceled_at": null,
+ "cancellation_reason": null,
+ "capture_method": "automatic",
+ "charges": {
+ "object": "list",
+ "data": [
+ {
+ "id": "ch_3NfRruAWOtgoysog1ptwVNHx",
+ "object": "charge",
+ "amount": 2000,
+ "amount_captured": 2000,
+ "amount_refunded": 0,
+ "application": null,
+ "application_fee": null,
+ "application_fee_amount": null,
+ "balance_transaction": "txn_3NfRruAWOtgoysog1mtFHzZr",
+ "billing_details": {
+ "address": {
+ "city": null,
+ "country": null,
+ "line1": null,
+ "line2": null,
+ "postal_code": null,
+ "state": null
+ },
+ "email": null,
+ "name": "Longbob Longsen",
+ "phone": null
+ },
+ "calculated_statement_descriptor": "SPREEDLY",
+ "captured": true,
+ "created": 1692123686,
+ "currency": "usd",
+ "customer": null,
+ "description": null,
+ "destination": null,
+ "dispute": null,
+ "disputed": false,
+ "failure_balance_transaction": null,
+ "failure_code": null,
+ "failure_message": null,
+ "fraud_details": {
+ },
+ "invoice": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "on_behalf_of": null,
+ "order": null,
+ "outcome": {
+ "network_status": "approved_by_network",
+ "reason": null,
+ "risk_level": "normal",
+ "risk_score": 34,
+ "seller_message": "Payment complete.",
+ "type": "authorized"
+ },
+ "paid": true,
+ "payment_intent": "pi_3NfRruAWOtgoysog1FxgDwtf",
+ "payment_method": "pm_1NfRruAWOtgoysogjdx336vt",
+ "payment_method_details": {
+ "card": {
+ "brand": "visa",
+ "checks": {
+ "address_line1_check": null,
+ "address_postal_code_check": null,
+ "cvc_check": "pass"
+ },
+ "country": "US",
+ "ds_transaction_id": null,
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": null,
+ "funding": "debit",
+ "installments": null,
+ "last4": "4242",
+ "mandate": null,
+ "moto": null,
+ "network": "visa",
+ "network_token": {
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": "OdTRtGskBulROtqa",
+ "last4": "5556",
+ "used": false
+ },
+ "network_transaction_id": "791008482116711",
+ "three_d_secure": null,
+ "wallet": null
+ },
+ "type": "card"
+ },
+ "receipt_email": null,
+ "receipt_number": null,
+ "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKeE76YGMgbjse9I0TM6LBZ6z9Y1XXMETb-LDQ5oyLVXQhIMltBU0qwDkNKpNvrIGvXOhYmhorDkkE36",
+ "refunded": false,
+ "refunds": {
+ "object": "list",
+ "data": [
+ ],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/charges/ch_3NfRruAWOtgoysog1ptwVNHx/refunds"
+ },
+ "review": null,
+ "shipping": null,
+ "source": null,
+ "source_transfer": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "succeeded",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/charges?payment_intent=pi_3NfRruAWOtgoysog1FxgDwtf"
+ },
+ "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke",
+ "confirmation_method": "automatic",
+ "created": 1692123686,
+ "currency": "usd",
+ "customer": null,
+ "description": null,
+ "invoice": null,
+ "last_payment_error": null,
+ "latest_charge": "ch_3NfRruAWOtgoysog1ptwVNHx",
+ "level3": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "next_action": null,
+ "on_behalf_of": null,
+ "payment_method": "pm_1NfRruAWOtgoysogjdx336vt",
+ "payment_method_options": {
+ "card": {
+ "installments": null,
+ "mandate_options": null,
+ "network": null,
+ "request_three_d_secure": "automatic"
+ }
+ },
+ "payment_method_types": [
+ "card"
+ ],
+ "processing": null,
+ "receipt_email": null,
+ "review": null,
+ "setup_future_usage": null,
+ "shipping": null,
+ "source": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "succeeded",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ RESPONSE
+ end
+
+ def successful_create_intent_manual_capture_response_with_network_token_fields
+ <<~RESPONSE
+ {
+ "id": "pi_3NfTpgAWOtgoysog1SqST5dL",
+ "object": "payment_intent",
+ "amount": 2000,
+ "amount_capturable": 2000,
+ "amount_details": {
+ "tip": {
+ }
+ },
+ "amount_received": 0,
+ "application": null,
+ "application_fee_amount": null,
+ "automatic_payment_methods": null,
+ "canceled_at": null,
+ "cancellation_reason": null,
+ "capture_method": "manual",
+ "charges": {
+ "object": "list",
+ "data": [
+ {
+ "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ",
+ "object": "charge",
+ "amount": 2000,
+ "amount_captured": 0,
+ "amount_refunded": 0,
+ "application": null,
+ "application_fee": null,
+ "application_fee_amount": null,
+ "balance_transaction": null,
+ "billing_details": {
+ "address": {
+ "city": null,
+ "country": null,
+ "line1": null,
+ "line2": null,
+ "postal_code": null,
+ "state": null
+ },
+ "email": null,
+ "name": "Longbob Longsen",
+ "phone": null
+ },
+ "calculated_statement_descriptor": "SPREEDLY",
+ "captured": false,
+ "created": 1692131237,
+ "currency": "gbp",
+ "customer": "cus_OSOcijtQkDdBbF",
+ "description": null,
+ "destination": null,
+ "dispute": null,
+ "disputed": false,
+ "failure_balance_transaction": null,
+ "failure_code": null,
+ "failure_message": null,
+ "fraud_details": {
+ },
+ "invoice": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "on_behalf_of": null,
+ "order": null,
+ "outcome": {
+ "network_status": "approved_by_network",
+ "reason": null,
+ "risk_level": "normal",
+ "risk_score": 24,
+ "seller_message": "Payment complete.",
+ "type": "authorized"
+ },
+ "paid": true,
+ "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL",
+ "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf",
+ "payment_method_details": {
+ "card": {
+ "brand": "visa",
+ "checks": {
+ "address_line1_check": null,
+ "address_postal_code_check": null,
+ "cvc_check": "pass"
+ },
+ "country": "US",
+ "ds_transaction_id": null,
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": null,
+ "funding": "debit",
+ "installments": null,
+ "last4": "4242",
+ "mandate": null,
+ "moto": null,
+ "network": "visa",
+ "network_token": {
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": "OdTRtGskBulROtqa",
+ "last4": "5556",
+ "used": false
+ },
+ "network_transaction_id": "791008482116711",
+ "three_d_secure": null,
+ "wallet": null
+ },
+ "type": "card"
+ },
+ "receipt_email": null,
+ "receipt_number": null,
+ "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKW_76YGMgZFk46uT_Y6LBZ51LZOrwdCQ0w176ShWIhNs2CXEh-L6A9pDYW33I_z6C6SenKNrWasw9Ie",
+ "refunded": false,
+ "refunds": {
+ "object": "list",
+ "data": [
+ ],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds"
+ },
+ "review": null,
+ "shipping": null,
+ "source": null,
+ "source_transfer": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "succeeded",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL"
+ },
+ "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke",
+ "confirmation_method": "manual",
+ "created": 1692131236,
+ "currency": "gbp",
+ "customer": "cus_OSOcijtQkDdBbF",
+ "description": null,
+ "invoice": null,
+ "last_payment_error": null,
+ "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ",
+ "level3": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "next_action": null,
+ "on_behalf_of": null,
+ "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf",
+ "payment_method_options": {
+ "card": {
+ "installments": null,
+ "mandate_options": null,
+ "network": null,
+ "request_three_d_secure": "automatic"
+ }
+ },
+ "payment_method_types": [
+ "card"
+ ],
+ "processing": null,
+ "receipt_email": null,
+ "review": null,
+ "setup_future_usage": null,
+ "shipping": null,
+ "source": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "requires_capture",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ RESPONSE
+ end
+
+ def successful_manual_capture_of_payment_intent_response_with_network_token_fields
+ <<-RESPONSE
+ {
+ "id": "pi_3NfTpgAWOtgoysog1SqST5dL",
+ "object": "payment_intent",
+ "amount": 2000,
+ "amount_capturable": 0,
+ "amount_details": {
+ "tip": {
+ }
+ },
+ "amount_received": 2000,
+ "application": null,
+ "application_fee_amount": null,
+ "automatic_payment_methods": null,
+ "canceled_at": null,
+ "cancellation_reason": null,
+ "capture_method": "manual",
+ "charges": {
+ "object": "list",
+ "data": [
+ {
+ "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ",
+ "object": "charge",
+ "amount": 2000,
+ "amount_captured": 2000,
+ "amount_refunded": 0,
+ "application": null,
+ "application_fee": null,
+ "application_fee_amount": null,
+ "balance_transaction": "txn_3NfTpgAWOtgoysog1ZTZXCvO",
+ "billing_details": {
+ "address": {
+ "city": null,
+ "country": null,
+ "line1": null,
+ "line2": null,
+ "postal_code": null,
+ "state": null
+ },
+ "email": null,
+ "name": "Longbob Longsen",
+ "phone": null
+ },
+ "calculated_statement_descriptor": "SPREEDLY",
+ "captured": true,
+ "created": 1692131237,
+ "currency": "gbp",
+ "customer": "cus_OSOcijtQkDdBbF",
+ "description": null,
+ "destination": null,
+ "dispute": null,
+ "disputed": false,
+ "failure_balance_transaction": null,
+ "failure_code": null,
+ "failure_message": null,
+ "fraud_details": {
+ },
+ "invoice": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "on_behalf_of": null,
+ "order": null,
+ "outcome": {
+ "network_status": "approved_by_network",
+ "reason": null,
+ "risk_level": "normal",
+ "risk_score": 24,
+ "seller_message": "Payment complete.",
+ "type": "authorized"
+ },
+ "paid": true,
+ "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL",
+ "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf",
+ "payment_method_details": {
+ "card": {
+ "brand": "visa",
+ "checks": {
+ "address_line1_check": null,
+ "address_postal_code_check": null,
+ "cvc_check": "pass"
+ },
+ "country": "US",
+ "ds_transaction_id": null,
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": null,
+ "funding": "debit",
+ "installments": null,
+ "last4": "4242",
+ "mandate": null,
+ "moto": null,
+ "network": "visa",
+ "network_token": {
+ "exp_month": 9,
+ "exp_year": 2030,
+ "fingerprint": "OdTRtGskBulROtqa",
+ "last4": "5556",
+ "used": false
+ },
+ "network_transaction_id": "791008482116711",
+ "three_d_secure": null,
+ "wallet": null
+ },
+ "type": "card"
+ },
+ "receipt_email": null,
+ "receipt_number": null,
+ "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKa_76YGMgZZ4Fl_Etg6LBYGcD6D2xFTlgp69zLDZz1ZToBrKKjxhRCpYcnLWInSmJZHcjcBdrhyAKGv",
+ "refunded": false,
+ "refunds": {
+ "object": "list",
+ "data": [
+ ],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds"
+ },
+ "review": null,
+ "shipping": null,
+ "source": null,
+ "source_transfer": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "succeeded",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL"
+ },
+ "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke",
+ "confirmation_method": "manual",
+ "created": 1692131236,
+ "currency": "gbp",
+ "customer": "cus_OSOcijtQkDdBbF",
+ "description": null,
+ "invoice": null,
+ "last_payment_error": null,
+ "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ",
+ "level3": null,
+ "livemode": false,
+ "metadata": {
+ },
+ "next_action": null,
+ "on_behalf_of": null,
+ "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf",
+ "payment_method_options": {
+ "card": {
+ "installments": null,
+ "mandate_options": null,
+ "network": null,
+ "request_three_d_secure": "automatic"
+ }
+ },
+ "payment_method_types": [
+ "card"
+ ],
+ "processing": null,
+ "receipt_email": null,
+ "review": null,
+ "setup_future_usage": null,
+ "shipping": null,
+ "source": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "succeeded",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ RESPONSE
+ end
+
def successful_create_intent_response_with_apple_pay_and_billing_address
<<-RESPONSE
{"id"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0mqdAWOtgoysog1EpiFDCD", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>15, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"apple_pay"=>{"type"=>"apple_pay"}, "dynamic_last4"=>"4242", "type"=>"apple_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKPTGn6IGMgZMGrHHLa46LBY0n2_9_Yar0wPTNukle4t28eKG0ZDZnxGYr6GyKn8VsKIEVjU4NkW8NHTL", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0mqdAWOtgoysog1HddFSKg/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0mqdAWOtgoysog1IQeiLiz"}, "client_secret"=>"pi_3N0mqdAWOtgoysog1IQeiLiz_secret_laDLUM6rVleLRqz0nMus9HktB", "confirmation_method"=>"automatic", "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}
From 71ee93e6ca65441f65d261c6f15d8c6df1aaa9e9 Mon Sep 17 00:00:00 2001
From: cristian
Date: Thu, 14 Sep 2023 09:41:04 -0500
Subject: [PATCH 164/390] Shift4: Fixing currency bug (#4887)
Summary:
------------------------------
Fixes a bug for Shift4 gateway to ensure that currency
code is downcased.
SER-811
Remote Test:
------------------------------
Finished in 34.37861 seconds.
34 tests, 121 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 47.584817 seconds.
5604 tests, 78022 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
766 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/shift4_v2.rb | 5 +++++
test/unit/gateways/securion_pay_test.rb | 9 +++++++++
test/unit/gateways/shift4_v2_test.rb | 8 +++++++-
4 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index c1d5da622d7..1b20cc283f5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,7 @@
* Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882
* CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881
* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867
+* Shift4: Fixing currency bug [Heavyblade] #4887
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/shift4_v2.rb b/lib/active_merchant/billing/gateways/shift4_v2.rb
index 536b779dfb3..f06208c2883 100644
--- a/lib/active_merchant/billing/gateways/shift4_v2.rb
+++ b/lib/active_merchant/billing/gateways/shift4_v2.rb
@@ -48,6 +48,11 @@ def scrub(transcript)
def json_error(raw_response)
super(raw_response, 'Shift4 V2')
end
+
+ def add_amount(post, money, options, include_currency = false)
+ super
+ post[:currency]&.upcase!
+ end
end
end
end
diff --git a/test/unit/gateways/securion_pay_test.rb b/test/unit/gateways/securion_pay_test.rb
index e812b4c8a84..5d3dc3b115b 100644
--- a/test/unit/gateways/securion_pay_test.rb
+++ b/test/unit/gateways/securion_pay_test.rb
@@ -394,6 +394,15 @@ def test_declined_request
assert_equal 'char_mApucpvVbCJgo7x09Je4n9gC', response.params['error']['chargeId']
end
+ def test_amount_currency_gets_downcased
+ @options[:currency] = 'USD'
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal 'usd', CGI.parse(data)['currency'].first
+ end.respond_with(successful_purchase_response)
+ end
+
private
def pre_scrubbed
diff --git a/test/unit/gateways/shift4_v2_test.rb b/test/unit/gateways/shift4_v2_test.rb
index 1c97361dd7b..7bc4747aff3 100644
--- a/test/unit/gateways/shift4_v2_test.rb
+++ b/test/unit/gateways/shift4_v2_test.rb
@@ -19,7 +19,13 @@ def test_invalid_raw_response
assert_match(/^Invalid response received from the Shift4 V2 API/, response.message)
end
- def test_ensure_does_not_respond_to_credit; end
+ def test_amount_gets_upcased_if_needed
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal 'USD', CGI.parse(data)['currency'].first
+ end.respond_with(successful_purchase_response)
+ end
private
From 120f2159e549220bb3adb5b91fd799b3354acc23 Mon Sep 17 00:00:00 2001
From: cristian
Date: Thu, 14 Sep 2023 16:18:53 -0500
Subject: [PATCH 165/390] Rapyd: fixing issue with json encoding and signatures
(#4892)
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 11 +++++++++++
test/remote/gateways/remote_rapyd_test.rb | 4 ++++
3 files changed, 16 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 1b20cc283f5..e9a1e200e32 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@
* CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881
* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867
* Shift4: Fixing currency bug [Heavyblade] #4887
+* Rapyd: fixing issue with json encoding and signatures [Heavyblade] #4892
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 29e395b4285..649a40df862 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -302,8 +302,19 @@ def commit(method, action, parameters)
)
end
+ # We need to revert the work of ActiveSupport JSON encoder to prevent discrepancies
+ # Between the signature and the actual request body
+ def revert_json_html_encoding!(string)
+ {
+ '\\u003e' => '>',
+ '\\u003c' => '<',
+ '\\u0026' => '&'
+ }.each { |k, v| string.gsub! k, v }
+ end
+
def api_request(method, url, rel_path, params)
params == {} ? body = '' : body = params.to_json
+ revert_json_html_encoding!(body) if defined?(ActiveSupport::JSON::Encoding) && ActiveSupport::JSON::Encoding.escape_html_entities_in_json
parse(ssl_request(method, url, body, headers(rel_path, body)))
end
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 18389ad05cb..c98be44be88 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -368,12 +368,16 @@ def test_successful_purchase_without_3ds_v2_gateway_specific
end
def test_successful_authorize_with_execute_threed
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = true
+ @options[:complete_payment_url] = 'http://www.google.com?param1=1¶m2=2'
options = @options.merge(pm_type: 'gb_visa_card', execute_threed: true)
response = @gateway.authorize(105000, @credit_card, options)
assert_success response
assert_equal 'ACT', response.params['data']['status']
assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
assert response.params['data']['redirect_url']
+ ensure
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false
end
def test_successful_purchase_without_cvv
From 8b9327020c4eb744e118d6731cd4279be3bf162b Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Wed, 20 Sep 2023 11:44:33 -0500
Subject: [PATCH 166/390] SumUp - Setup, Scrub and Purchase build (#4890)
Description
-------------------------
Setup, scrub and purchase method to SumUp Gateway adapter with the basic information needed.
This are the relevant links to review the initial implementation:
[SumUp REST API](https://developer.sumup.com/docs/api/sum-up-rest-api/)
[Make a payment with a card entered by a customer](https://developer.sumup.com/docs/online-payments/guides/single-payment/)
[Checkouts](https://developer.sumup.com/docs/api/checkouts/)
Tickets for Spreedly reference
SER-764
SER-712
SER-711
Unit test
-------------------------
Finished in 31.732818 seconds.
5611 tests, 78033 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
Remote test
-------------------------
100% passed
176.82 tests/s, 2459.06 assertions/s
Rubocop
-------------------------
Inspecting 769 files
769 files inspected, no offenses detected
Co-authored-by: Luis
Co-authored-by: Nick Ashton
---
CHANGELOG | 1 +
.../billing/gateways/sum_up.rb | 183 ++++++++
test/fixtures.yml | 4 +
test/remote/gateways/remote_sum_up_test.rb | 112 +++++
test/unit/gateways/sum_up_test.rb | 414 ++++++++++++++++++
5 files changed, 714 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/sum_up.rb
create mode 100644 test/remote/gateways/remote_sum_up_test.rb
create mode 100644 test/unit/gateways/sum_up_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index e9a1e200e32..69294d75c4c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,7 @@
* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867
* Shift4: Fixing currency bug [Heavyblade] #4887
* Rapyd: fixing issue with json encoding and signatures [Heavyblade] #4892
+* SumUp: Setup, Scrub and Purchase build [sinourain] #4890
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/sum_up.rb b/lib/active_merchant/billing/gateways/sum_up.rb
new file mode 100644
index 00000000000..1b0b8635cfe
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/sum_up.rb
@@ -0,0 +1,183 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SumUpGateway < Gateway
+ self.live_url = 'https://api.sumup.com/v0.1/'
+
+ self.supported_countries = %w(AT BE BG BR CH CL CO CY CZ DE DK EE ES FI FR
+ GB GR HR HU IE IT LT LU LV MT NL NO PL PT RO
+ SE SI SK US)
+ self.currencies_with_three_decimal_places = %w(EUR BGN BRL CHF CZK DKK GBP
+ HUF NOK PLN SEK USD)
+ self.default_currency = 'USD'
+
+ self.homepage_url = 'https://www.sumup.com/'
+ self.display_name = 'SumUp'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ multiple_invalid_parameters: 'MULTIPLE_INVALID_PARAMETERS'
+ }
+
+ def initialize(options = {})
+ requires!(options, :access_token, :pay_to_email)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ MultiResponse.run do |r|
+ r.process { create_checkout(money, payment, options) } unless options[:checkout_id]
+ r.process { complete_checkout(options[:checkout_id] || r.params['id'], payment, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]').
+ gsub(%r(("pay_to_email\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def create_checkout(money, payment, options)
+ post = {}
+
+ add_merchant_data(post, options)
+ add_invoice(post, money, options)
+ add_address(post, options)
+ add_customer_data(post, payment, options)
+
+ commit('checkouts', post)
+ end
+
+ def complete_checkout(checkout_id, payment, options = {})
+ post = {}
+
+ add_payment(post, payment, options)
+
+ commit('checkouts/' + checkout_id, post, :put)
+ end
+
+ def add_customer_data(post, payment, options)
+ post[:customer_id] = options[:customer_id]
+ post[:personal_details] = {
+ email: options[:email],
+ first_name: payment&.first_name,
+ last_name: payment&.last_name,
+ tax_id: options[:tax_id]
+ }
+ end
+
+ def add_merchant_data(post, options)
+ # Required field: pay_to_email
+ # Description: Email address of the merchant to whom the payment is made.
+ post[:pay_to_email] = @options[:pay_to_email]
+ end
+
+ def add_address(post, options)
+ post[:personal_details] ||= {}
+ if address = (options[:billing_address] || options[:shipping_address] || options[:address])
+ post[:personal_details][:address] = {
+ city: address[:city],
+ state: address[:state],
+ country: address[:country],
+ line_1: address[:address1],
+ postal_code: address[:zip]
+ }
+ end
+ end
+
+ def add_invoice(post, money, options)
+ payment_currency = options[:currency] || currency(money)
+ post[:checkout_reference] = options[:order_id]
+ post[:amount] = localized_amount(money, payment_currency)
+ post[:currency] = payment_currency
+ post[:description] = options[:description]
+ end
+
+ def add_payment(post, payment, options)
+ post[:payment_type] = options[:payment_type] || 'card'
+
+ post[:card] = {
+ name: payment.name,
+ number: payment.number,
+ expiry_month: format(payment.month, :two_digits),
+ expiry_year: payment.year,
+ cvv: payment.verification_value
+ }
+ end
+
+ def commit(action, post, method = :post)
+ response = api_request(action, post.compact, method)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def api_request(action, post, method)
+ begin
+ raw_response = ssl_request(method, live_url + action, post.to_json, auth_headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+
+ response = parse(raw_response)
+ # Multiple invalid parameters
+ response = format_multiple_errors(response) if raw_response.include?('error_code') && response.is_a?(Array)
+
+ return response.symbolize_keys
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ response[:status] == 'PENDING'
+ end
+
+ def message_from(response)
+ return response[:status] if success_from(response)
+
+ response[:message] || response[:error_message]
+ end
+
+ def authorization_from(response)
+ response[:id]
+ end
+
+ def auth_headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{options[:access_token]}"
+ }
+ end
+
+ def error_code_from(response)
+ response[:error_code] unless success_from(response)
+ end
+
+ def format_multiple_errors(responses)
+ errors = responses.map do |response|
+ { error_code: response['error_code'], param: response['param'] }
+ end
+
+ {
+ error_code: STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters],
+ message: 'Validation error',
+ errors: errors
+ }
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index c1db9c47cbf..4f4e225cc5b 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -1327,6 +1327,10 @@ stripe_verified_bank_account:
customer_id: "cus_7s22nNueP2Hjj6"
bank_account_id: "ba_17cHxeAWOtgoysogv3NM8CJ1"
+sum_up:
+ access_token: SOMECREDENTIAL
+ pay_to_email: SOMECREDENTIAL
+
# Working credentials, no need to replace
swipe_checkout:
login: 2077103073D8B5
diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb
new file mode 100644
index 00000000000..4ec7beaa8a9
--- /dev/null
+++ b/test/remote/gateways/remote_sum_up_test.rb
@@ -0,0 +1,112 @@
+require 'test_helper'
+
+class RemoteSumUpTest < Test::Unit::TestCase
+ def setup
+ @gateway = SumUpGateway.new(fixtures(:sum_up))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('55555555555555555')
+ @options = {
+ payment_type: 'card',
+ billing_address: address,
+ description: 'Store Purchase',
+ order_id: SecureRandom.uuid
+ }
+ end
+
+ def test_handle_credentials_error
+ gateway = SumUpGateway.new({ access_token: 'sup_sk_xx', pay_to_email: 'example@example.com' })
+ response = gateway.purchase(@amount, @visa_card, @options)
+
+ assert_equal('invalid access token', response.message)
+ end
+
+ def test_handle_pay_to_email_credential_error
+ gateway = SumUpGateway.new(fixtures(:sum_up).merge(pay_to_email: 'example@example.com'))
+ response = gateway.purchase(@amount, @visa_card, @options)
+
+ assert_equal('Validation error', response.message)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ assert_equal @options[:order_id], response.params['checkout_reference']
+ refute_empty response.params['id']
+ refute_empty response.params['transactions']
+ refute_empty response.params['transactions'].first['id']
+ assert_equal 'PENDING', response.params['transactions'].first['status']
+ end
+
+ def test_successful_purchase_with_existing_checkout
+ existing_checkout = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success existing_checkout
+ refute_empty existing_checkout.params['id']
+ @options[:checkout_id] = existing_checkout.params['id']
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ assert_equal @options[:order_id], response.params['checkout_reference']
+ refute_empty response.params['id']
+ assert_equal existing_checkout.params['id'], response.params['id']
+ refute_empty response.params['transactions']
+ assert_equal response.params['transactions'].count, 2
+ refute_empty response.params['transactions'].last['id']
+ assert_equal 'PENDING', response.params['transactions'].last['status']
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ email: 'joe@example.com',
+ tax_id: '12345',
+ redirect_url: 'https://checkout.example.com',
+ return_url: 'https://checkout.example.com',
+ billing_address: address,
+ order_id: SecureRandom.uuid,
+ currency: 'USD',
+ description: 'Sample description',
+ payment_type: 'card'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Validation error', response.message
+ assert_equal 'The value located under the \'$.card.number\' path is not a valid card number', response.params['detail']
+ end
+
+ def test_failed_purchase_invalid_customer_id
+ options = @options.merge!(customer_id: 'customer@example.com', payment_type: 'card')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Validation error', response.message
+ assert_equal 'customer_id', response.params['param']
+ end
+
+ def test_failed_purchase_invalid_currency
+ options = @options.merge!(currency: 'EUR')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Given currency differs from merchant\'s country currency', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:pay_to_email], transcript)
+ end
+end
diff --git a/test/unit/gateways/sum_up_test.rb b/test/unit/gateways/sum_up_test.rb
new file mode 100644
index 00000000000..2ec23c1c72f
--- /dev/null
+++ b/test/unit/gateways/sum_up_test.rb
@@ -0,0 +1,414 @@
+require 'test_helper'
+
+class SumUpTest < Test::Unit::TestCase
+ def setup
+ @gateway = SumUpGateway.new(
+ access_token: 'sup_sk_ABC123',
+ pay_to_email: 'example@example.com'
+ )
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ payment_type: 'card',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_request).returns(successful_create_checkout_response)
+ @gateway.expects(:ssl_request).returns(successful_complete_checkout_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+
+ assert_equal 'PENDING', response.message
+ refute_empty response.params
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_request).returns(failed_complete_checkout_array_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+
+ assert_equal SumUpGateway::STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], response.error_code
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_success_from
+ response = @gateway.send(:parse, successful_complete_checkout_response)
+ success_from = @gateway.send(:success_from, response.symbolize_keys)
+ assert_equal true, success_from
+ end
+
+ def test_message_from
+ response = @gateway.send(:parse, successful_complete_checkout_response)
+ message_from = @gateway.send(:message_from, response.symbolize_keys)
+ assert_equal 'PENDING', message_from
+ end
+
+ def test_authorization_from
+ response = @gateway.send(:parse, successful_complete_checkout_response)
+ authorization_from = @gateway.send(:authorization_from, response.symbolize_keys)
+ assert_equal '8d8336a1-32e2-4f96-820a-5c9ee47e76fc', authorization_from
+ end
+
+ def test_format_multiple_errors
+ responses = @gateway.send(:parse, failed_complete_checkout_array_response)
+ error_code = @gateway.send(:format_multiple_errors, responses)
+ assert_equal format_multiple_errors_response, error_code
+ end
+
+ def test_error_code_from
+ response = @gateway.send(:parse, failed_complete_checkout_response)
+ error_code_from = @gateway.send(:error_code_from, response.symbolize_keys)
+ assert_equal 'CHECKOUT_SESSION_IS_EXPIRED', error_code_from
+ end
+
+ private
+
+ def pre_scrubbed
+ <<-PRE_SCRUBBED
+ opening connection to api.sumup.com:443...
+ opened
+ starting SSL for api.sumup.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- \"POST /v0.1/checkouts HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer sup_sk_ABC123\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api.sumup.com\\r\
+ Content-Length: 422\\r\
+ \\r\
+ \"
+ <- \"{\\\"pay_to_email\\\":\\\"example@example.com\\\",\\\"redirect_url\\\":null,\\\"return_url\\\":null,\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"USD\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"personal_details\\\":{\\\"address\\\":{\\\"city\\\":\\\"Ottawa\\\",\\\"state\\\":\\\"ON\\\",\\\"country\\\":\\\"CA\\\",\\\"line_1\\\":\\\"456 My Street\\\",\\\"postal_code\\\":\\\"K1C2N6\\\"},\\\"email\\\":null,\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\",\\\"tax_id\\\":null},\\\"customer_id\\\":null}\"
+ -> \"HTTP/1.1 201 Created\\r\
+ \"
+ -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\
+ \"
+ -> \"Content-Type: application/json;charset=UTF-8\\r\
+ \"
+ -> \"Content-Length: 360\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"x-powered-by: Express\\r\
+ \"
+ -> \"access-control-allow-origin: *\\r\
+ \"
+ -> \"x-fong-id: 723b20084f2c, 723b20084f2c, 723b20084f2c 5df223126f1c\\r\
+ \"
+ -> \"cf-cache-status: DYNAMIC\\r\
+ \"
+ -> \"vary: Accept-Encoding\\r\
+ \"
+ -> \"apigw-requestid: LOyHiheuDoEEJSA=\\r\
+ \"
+ -> \"set-cookie: __cf_bm=1unGPonmyW_H8VRqo.O6h20hrSJ_0GtU3VqD9i3uYkI-1694668540-0-AaYQ1MVLyKxcwSNy8oNS5t/uVdk5ZU6aFPI/yvVcohm0Fm+Kltk55ngpG/Bms3cvRtxVX9DidO4ziiP2IsQcM41uJZq6TrcgLUD7KbJfJwV8; path=/; expires=Thu, 14-Sep-23 05:45:40 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"x-op-gateway: true\\r\
+ \"
+ -> \"Set-Cookie: __cf_bm=OYzsPf_HGhiUfF0EETH_zOM74zPZpYhmqI.FJxehmpY-1694668541-0-AWVAexX304k53VB3HIhdyg+uP4ElzrS23jwIAdPGccfN5DM/81TE0ioW7jb7kA3jCZDuGENGofaZz0pBwSr66lRiWu9fdAzdUIbwNDOBivWY; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"Server: cloudflare\\r\
+ \"
+ -> \"CF-RAY: 80662747af463995-BOG\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 360 bytes...
+ -> \"{\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":1.0,\\\"currency\\\":\\\"USD\\\",\\\"pay_to_email\\\":\\\"example@example.com\\\",\\\"merchant_code\\\":\\\"MTVU2XGK\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"id\\\":\\\"70f71869-ed81-40b0-b2d8-c98f80f4c39d\\\",\\\"status\\\":\\\"PENDING\\\",\\\"date\\\":\\\"2023-09-14T05:15:40.000+00:00\\\",\\\"merchant_name\\\":\\\"Spreedly\\\",\\\"purpose\\\":\\\"CHECKOUT\\\",\\\"transactions\\\":[]}\"
+ read 360 bytes
+ Conn close
+ opening connection to api.sumup.com:443...
+ opened
+ starting SSL for api.sumup.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- \"PUT /v0.1/checkouts/70f71869-ed81-40b0-b2d8-c98f80f4c39d HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer sup_sk_ABC123\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api.sumup.com\\r\
+ Content-Length: 136\\r\
+ \\r\
+ \"
+ <- \"{\\\"payment_type\\\":\\\"card\\\",\\\"card\\\":{\\\"name\\\":\\\"Longbob Longsen\\\",\\\"number\\\":\\\"4000100011112224\\\",\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"24\\\",\\\"cvv\\\":\\\"123\\\"}}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\
+ \"
+ -> \"Content-Type: application/json\\r\
+ \"
+ -> \"Transfer-Encoding: chunked\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"x-powered-by: Express\\r\
+ \"
+ -> \"access-control-allow-origin: *\\r\
+ \"
+ -> \"x-fong-id: 8a116d29420e, 8a116d29420e, 8a116d29420e a534b6871710\\r\
+ \"
+ -> \"cf-cache-status: DYNAMIC\\r\
+ \"
+ -> \"vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers\\r\
+ \"
+ -> \"apigw-requestid: LOyHoggJjoEEMxA=\\r\
+ \"
+ -> \"set-cookie: __cf_bm=AoWMlPJNg1_THatbGnZchhj7K0QaqwlU0SqYrlDJ.78-1694668541-0-AdHrPpd/94p0oyLJWzsEUYatqVZMiJ0i1BJICEiprAo8AMDiya+V3OjljwbCpaNQNAPFVJpX1S4KxIFEUEeeNfAJv1HOjjaToNYhJuhLQ1NT; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"x-op-gateway: true\\r\
+ \"
+ -> \"Set-Cookie: __cf_bm=UcJRX.Pe233lWIyCGlqNICBOhruxwESN41sDCDfzQBQ-1694668541-0-ASJ/Wl84HRovjKIq/p+Re8GrxkxHM1XvbDE/mXT/4r7PYA1cpTzG2uhp7WEkqVpEj7FCb2ahP5ExApEWWx0JDut8Uhx1SeQJHYFR/26E8BTv; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"Server: cloudflare\\r\
+ \"
+ -> \"CF-RAY: 8066274e3a95399b-BOG\\r\
+ \"
+ -> \"Content-Encoding: gzip\\r\
+ \"
+ -> \"\\r\
+ \"
+ -> \"1bc\\r\
+ \"
+ reading 444 bytes...
+ -> \"\\x1F\\x8B\\b\\x00\\x00\\x00\\x00\\x00\\x00\\x03|\\x92[\\x8B\\xDB0\\x10\\x85\\xFFJ\\x99\\xD7ZA\\x92\\x15G\\xD6S!1\\xDB\\xB2\\xCD\\x85\\x8D]RJ1\\xB2$wMm\\xD9Hr\\xC1,\\xFB\\xDF\\x8B\\xF6R\\x1A\\xBA\\xF4\\xF50G\\xF3\\xCD9z\\x00uo\\xD4\\xCFq\\x0E\\xB53\\xADq\\xC6*\\x03\\x02\\bS\\x9C\\xD0V!\\x96\\xF1\\x1C\\xB1\\x86K$\\x99\\xDE \\xA3)i\\xDAT\\xA5y\\xDBB\\x02r\\x18g\\e@\\x90\\x15N@\\xCD.\\xDA\\x17\\x10P\\x9Dw\\x90\\xC0$\\x97:\\x8C\\xB5\\x19d\\xD7\\x83\\x80\\xCE\\x06\\xF3\\xC3\\xC9\\xD0\\x8D\\xD6\\x7F\\xF0\\x933F\\xF7\\xCBJ\\x8D\\x03$0\\x18\\xA7\\xEE\\xA5\\r\\xB5\\x1Au\\xDC\\xBF/\\xBFT\\xF4rs\\v\\th\\xE3\\x95\\xEB\\xA6h\\x03\\x01\\xE70:\\xF3\\xEE4\\xC7qo \\x81N\\x83\\x80\\rn7\\x84g92\\x9A\\x13\\xC4p\\x83QC5G*\\xE7-\\xC7-Si\\xAE!\\x01\\x1Fd\\x98=\\b8\\x15\\x87\\xDD\\xA7\\xC3M|]\\x86\\xB8\\x8Fb\\x9A\\\"\\x9C#\\xC2J\\xBC\\x16d-\\x18^a\\x8C\\xDFc,0\\xFE\\x9B\\xCF\\xCA!\\xCE\\x9F_\\xF0\\xE3\\x95\\xB3\\x9BF\\x1F\\xC5\\xED\\xC7b{{\\xACJH 8i\\xBDTO\\xB7\\x82\\xF8\\xF6\\xF0\\x8C\\x893\\xCD\\x15[S\\xD4\\xB2\\xD4 \\x96R\\x8E8\\xC7\"
+ -> \")\\xE2\\xBAU\\x9A\\xF0\\x94\\xD0&\\xBD6\\xBF\\xE6Q\\xEE(\\xADN\\x97\\xCF\\x97\\xF2\\xFFa]\\x15\\xF2K\\x86\\xFAU\\xC0Q\\b\\xDDt-\\xFCSY\\xE8\\x06\\xE3\\x83\\x1C\\xA673!+\\xC6\\xF3?\\x99\\xBC\\x91\\xE6$\\x97\\xC1\\xD8P\\x87e\\x8A`\\xC5\\xF6\\xB8\\x87\\x04\\x8C\\rn\\xA9\\x87g\\xD8mu.\\x8F\\xFB\\xE2\\xAE.\\x0E\\xE5\\xDD\\xD7X\\xA0\\xF5A\\xF6}\\xF4\\xF9Z\\xBD\\xE0'O\\xBF\\xC5Y\\xD9\\xD71\\xB95\\xC9\\xE8\\x06\\xA7,\\xA3\\x8F\\xDF\\x1F\\x7F\\x03\\x00\\x00\\xFF\\xFF\\x03\\x00\\xB5\\x12\\xCA\\x11\\xB3\\x02\\x00\\x00\"
+ read 444 bytes
+ reading 2 bytes...
+ -> \"\\r\
+ \"
+ read 2 bytes
+ -> \"0\\r\
+ \"
+ -> \"\\r\
+ \"
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed
+ <<-POST_SCRUBBED
+ opening connection to api.sumup.com:443...
+ opened
+ starting SSL for api.sumup.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- \"POST /v0.1/checkouts HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer [FILTERED]\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api.sumup.com\\r\
+ Content-Length: 422\\r\
+ \\r\
+ \"
+ <- \"{\\\"pay_to_email\\\":\\\"[FILTERED]\",\\\"redirect_url\\\":null,\\\"return_url\\\":null,\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"USD\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"personal_details\\\":{\\\"address\\\":{\\\"city\\\":\\\"Ottawa\\\",\\\"state\\\":\\\"ON\\\",\\\"country\\\":\\\"CA\\\",\\\"line_1\\\":\\\"456 My Street\\\",\\\"postal_code\\\":\\\"K1C2N6\\\"},\\\"email\\\":null,\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\",\\\"tax_id\\\":null},\\\"customer_id\\\":null}\"
+ -> \"HTTP/1.1 201 Created\\r\
+ \"
+ -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\
+ \"
+ -> \"Content-Type: application/json;charset=UTF-8\\r\
+ \"
+ -> \"Content-Length: 360\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"x-powered-by: Express\\r\
+ \"
+ -> \"access-control-allow-origin: *\\r\
+ \"
+ -> \"x-fong-id: 723b20084f2c, 723b20084f2c, 723b20084f2c 5df223126f1c\\r\
+ \"
+ -> \"cf-cache-status: DYNAMIC\\r\
+ \"
+ -> \"vary: Accept-Encoding\\r\
+ \"
+ -> \"apigw-requestid: LOyHiheuDoEEJSA=\\r\
+ \"
+ -> \"set-cookie: __cf_bm=1unGPonmyW_H8VRqo.O6h20hrSJ_0GtU3VqD9i3uYkI-1694668540-0-AaYQ1MVLyKxcwSNy8oNS5t/uVdk5ZU6aFPI/yvVcohm0Fm+Kltk55ngpG/Bms3cvRtxVX9DidO4ziiP2IsQcM41uJZq6TrcgLUD7KbJfJwV8; path=/; expires=Thu, 14-Sep-23 05:45:40 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"x-op-gateway: true\\r\
+ \"
+ -> \"Set-Cookie: __cf_bm=OYzsPf_HGhiUfF0EETH_zOM74zPZpYhmqI.FJxehmpY-1694668541-0-AWVAexX304k53VB3HIhdyg+uP4ElzrS23jwIAdPGccfN5DM/81TE0ioW7jb7kA3jCZDuGENGofaZz0pBwSr66lRiWu9fdAzdUIbwNDOBivWY; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"Server: cloudflare\\r\
+ \"
+ -> \"CF-RAY: 80662747af463995-BOG\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 360 bytes...
+ -> \"{\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":1.0,\\\"currency\\\":\\\"USD\\\",\\\"pay_to_email\\\":\\\"[FILTERED]\",\\\"merchant_code\\\":\\\"MTVU2XGK\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"id\\\":\\\"70f71869-ed81-40b0-b2d8-c98f80f4c39d\\\",\\\"status\\\":\\\"PENDING\\\",\\\"date\\\":\\\"2023-09-14T05:15:40.000+00:00\\\",\\\"merchant_name\\\":\\\"Spreedly\\\",\\\"purpose\\\":\\\"CHECKOUT\\\",\\\"transactions\\\":[]}\"
+ read 360 bytes
+ Conn close
+ opening connection to api.sumup.com:443...
+ opened
+ starting SSL for api.sumup.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- \"PUT /v0.1/checkouts/70f71869-ed81-40b0-b2d8-c98f80f4c39d HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer [FILTERED]\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api.sumup.com\\r\
+ Content-Length: 136\\r\
+ \\r\
+ \"
+ <- \"{\\\"payment_type\\\":\\\"card\\\",\\\"card\\\":{\\\"name\\\":\\\"Longbob Longsen\\\",\\\"number\\\":\\\"[FILTERED]\",\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"24\\\",\\\"cvv\\\":\\\"[FILTERED]\"}}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\
+ \"
+ -> \"Content-Type: application/json\\r\
+ \"
+ -> \"Transfer-Encoding: chunked\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"x-powered-by: Express\\r\
+ \"
+ -> \"access-control-allow-origin: *\\r\
+ \"
+ -> \"x-fong-id: 8a116d29420e, 8a116d29420e, 8a116d29420e a534b6871710\\r\
+ \"
+ -> \"cf-cache-status: DYNAMIC\\r\
+ \"
+ -> \"vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers\\r\
+ \"
+ -> \"apigw-requestid: LOyHoggJjoEEMxA=\\r\
+ \"
+ -> \"set-cookie: __cf_bm=AoWMlPJNg1_THatbGnZchhj7K0QaqwlU0SqYrlDJ.78-1694668541-0-AdHrPpd/94p0oyLJWzsEUYatqVZMiJ0i1BJICEiprAo8AMDiya+V3OjljwbCpaNQNAPFVJpX1S4KxIFEUEeeNfAJv1HOjjaToNYhJuhLQ1NT; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"x-op-gateway: true\\r\
+ \"
+ -> \"Set-Cookie: __cf_bm=UcJRX.Pe233lWIyCGlqNICBOhruxwESN41sDCDfzQBQ-1694668541-0-ASJ/Wl84HRovjKIq/p+Re8GrxkxHM1XvbDE/mXT/4r7PYA1cpTzG2uhp7WEkqVpEj7FCb2ahP5ExApEWWx0JDut8Uhx1SeQJHYFR/26E8BTv; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\
+ \"
+ -> \"Server: cloudflare\\r\
+ \"
+ -> \"CF-RAY: 8066274e3a95399b-BOG\\r\
+ \"
+ -> \"Content-Encoding: gzip\\r\
+ \"
+ -> \"\\r\
+ \"
+ -> \"1bc\\r\
+ \"
+ reading 444 bytes...
+ -> \"\\x1F\\x8B\\b\\x00\\x00\\x00\\x00\\x00\\x00\\x03|\\x92[\\x8B\\xDB0\\x10\\x85\\xFFJ\\x99\\xD7ZA\\x92\\x15G\\xD6S!1\\xDB\\xB2\\xCD\\x85\\x8D]RJ1\\xB2$wMm\\xD9Hr\\xC1,\\xFB\\xDF\\x8B\\xF6R\\x1A\\xBA\\xF4\\xF50G\\xF3\\xCD9z\\x00uo\\xD4\\xCFq\\x0E\\xB53\\xADq\\xC6*\\x03\\x02\\bS\\x9C\\xD0V!\\x96\\xF1\\x1C\\xB1\\x86K$\\x99\\xDE \\xA3)i\\xDAT\\xA5y\\xDBB\\x02r\\x18g\\e@\\x90\\x15N@\\xCD.\\xDA\\x17\\x10P\\x9Dw\\x90\\xC0$\\x97:\\x8C\\xB5\\x19d\\xD7\\x83\\x80\\xCE\\x06\\xF3\\xC3\\xC9\\xD0\\x8D\\xD6\\x7F\\xF0\\x933F\\xF7\\xCBJ\\x8D\\x03$0\\x18\\xA7\\xEE\\xA5\\r\\xB5\\x1Au\\xDC\\xBF/\\xBFT\\xF4rs\\v\\th\\xE3\\x95\\xEB\\xA6h\\x03\\x01\\xE70:\\xF3\\xEE4\\xC7qo \\x81N\\x83\\x80\\rn7\\x84g92\\x9A\\x13\\xC4p\\x83QC5G*\\xE7-\\xC7-Si\\xAE!\\x01\\x1Fd\\x98=\\b8\\x15\\x87\\xDD\\xA7\\xC3M|]\\x86\\xB8\\x8Fb\\x9A\\\"\\x9C#\\xC2J\\xBC\\x16d-\\x18^a\\x8C\\xDFc,0\\xFE\\x9B\\xCF\\xCA!\\xCE\\x9F_\\xF0\\xE3\\x95\\xB3\\x9BF\\x1F\\xC5\\xED\\xC7b{{\\xACJH 8i\\xBDTO\\xB7\\x82\\xF8\\xF6\\xF0\\x8C\\x893\\xCD\\x15[S\\xD4\\xB2\\xD4 \\x96R\\x8E8\\xC7\"
+ -> \")\\xE2\\xBAU\\x9A\\xF0\\x94\\xD0&\\xBD6\\xBF\\xE6Q\\xEE(\\xADN\\x97\\xCF\\x97\\xF2\\xFFa]\\x15\\xF2K\\x86\\xFAU\\xC0Q\\b\\xDDt-\\xFCSY\\xE8\\x06\\xE3\\x83\\x1C\\xA673!+\\xC6\\xF3?\\x99\\xBC\\x91\\xE6$\\x97\\xC1\\xD8P\\x87e\\x8A`\\xC5\\xF6\\xB8\\x87\\x04\\x8C\\rn\\xA9\\x87g\\xD8mu.\\x8F\\xFB\\xE2\\xAE.\\x0E\\xE5\\xDD\\xD7X\\xA0\\xF5A\\xF6}\\xF4\\xF9Z\\xBD\\xE0'O\\xBF\\xC5Y\\xD9\\xD71\\xB95\\xC9\\xE8\\x06\\xA7,\\xA3\\x8F\\xDF\\x1F\\x7F\\x03\\x00\\x00\\xFF\\xFF\\x03\\x00\\xB5\\x12\\xCA\\x11\\xB3\\x02\\x00\\x00\"
+ read 444 bytes
+ reading 2 bytes...
+ -> \"\\r\
+ \"
+ read 2 bytes
+ -> \"0\\r\
+ \"
+ -> \"\\r\
+ \"
+ Conn close
+ POST_SCRUBBED
+ end
+
+ def successful_create_checkout_response
+ <<-RESPONSE
+ {
+ "checkout_reference": "e86ba553-b3d0-49f6-b4b5-18bd67502db2",
+ "amount": 1.0,
+ "currency": "USD",
+ "pay_to_email": "example@example.com",
+ "merchant_code": "ABC123",
+ "description": "Store Purchase",
+ "id": "8d8336a1-32e2-4f96-820a-5c9ee47e76fc",
+ "status": "PENDING",
+ "date": "2023-09-14T00:26:37.000+00:00",
+ "merchant_name": "Spreedly",
+ "purpose": "CHECKOUT",
+ "transactions": []
+ }
+ RESPONSE
+ end
+
+ def successful_complete_checkout_response
+ <<-RESPONSE
+ {
+ "checkout_reference": "e86ba553-b3d0-49f6-b4b5-18bd67502db2",
+ "amount": 1.0,
+ "currency": "USD",
+ "pay_to_email": "example@example.com",
+ "merchant_code": "ABC123",
+ "description": "Store Purchase",
+ "id": "8d8336a1-32e2-4f96-820a-5c9ee47e76fc",
+ "status": "PENDING",
+ "date": "2023-09-14T00: 26: 37.000+00: 00",
+ "merchant_name": "Spreedly",
+ "purpose": "CHECKOUT",
+ "transactions": [{
+ "id": "1bce6072-1865-4a90-887f-cb7fda97b300",
+ "transaction_code": "TDMNUPS33H",
+ "merchant_code": "MTVU2XGK",
+ "amount": 1.0,
+ "vat_amount": 0.0,
+ "tip_amount": 0.0,
+ "currency": "USD",
+ "timestamp": "2023-09-14T00:26:38.420+00:00",
+ "status": "PENDING",
+ "payment_type": "ECOM",
+ "entry_mode": "CUSTOMER_ENTRY",
+ "installments_count": 1,
+ "internal_id": 5162527027
+ }]
+ }
+ RESPONSE
+ end
+
+ def failed_complete_checkout_response
+ <<-RESPONSE
+ {
+ "type": "https://developer.sumup.com/docs/problem/session-expired/",
+ "title": "Conflict",
+ "status": 409,
+ "detail": "The checkout session 79c866c2-0b2d-470d-925a-37ddc8855ec2 is expired",
+ "instance": "79a4ed94d177, 79a4ed94d177 c24ac3136c71",
+ "error_code": "CHECKOUT_SESSION_IS_EXPIRED",
+ "message": "Checkout is expired"
+ }
+ RESPONSE
+ end
+
+ def failed_complete_checkout_array_response
+ <<-RESPONSE
+ [
+ {
+ "message": "Validation error",
+ "param": "card",
+ "error_code": "The card is expired"
+ }
+ ]
+ RESPONSE
+ end
+
+ def format_multiple_errors_response
+ {
+ error_code: 'MULTIPLE_INVALID_PARAMETERS',
+ message: 'Validation error',
+ errors: [{ error_code: 'The card is expired', param: 'card' }]
+ }
+ end
+end
From 8e375ecca30a89bece509bc6166adbd1f236e584 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Wed, 20 Sep 2023 14:14:23 -0500
Subject: [PATCH 167/390] XpayGateway: initia Setup (#4889)
Description
-------------------------
[SER-763](https://spreedly.atlassian.net/browse/SER-763)
This commit add the initial setup for the new Xpay gateway
Unit test
-------------------------
Finished in 0.557754 seconds.
2 tests, 4 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
3.59 tests/s, 7.17 assertions/s
Remote test
-------------------------
Finished in 0.557754 seconds.
2 tests, 4 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
3.59 tests/s, 7.17 assertions/s
Rubocop
-------------------------
769 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/xpay.rb | 135 +++++++++++++++++++
test/fixtures.yml | 3 +
test/remote/gateways/remote_xpay_test.rb | 15 +++
test/unit/gateways/xpay_test.rb | 57 ++++++++
5 files changed, 211 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/xpay.rb
create mode 100644 test/remote/gateways/remote_xpay_test.rb
create mode 100644 test/unit/gateways/xpay_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index 69294d75c4c..2b271ce62f8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,7 @@
* Shift4: Fixing currency bug [Heavyblade] #4887
* Rapyd: fixing issue with json encoding and signatures [Heavyblade] #4892
* SumUp: Setup, Scrub and Purchase build [sinourain] #4890
+* XpayGateway: Initial setup [javierpedrozaing] #4889
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/xpay.rb b/lib/active_merchant/billing/gateways/xpay.rb
new file mode 100644
index 00000000000..6aaefc99154
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/xpay.rb
@@ -0,0 +1,135 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class XpayGateway < Gateway
+ self.display_name = 'XPay Gateway'
+ self.homepage_url = 'https://developer.nexi.it/en'
+
+ self.test_url = 'https://stg-ta.nexigroup.com/api/phoenix-0.0/psp/api/v1/'
+ self.live_url = 'https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1/'
+
+ self.supported_countries = %w(AT BE CY EE FI FR DE GR IE IT LV LT LU MT PT SK SI ES BG HR DK NO PL RO RO SE CH HU)
+ self.default_currency = 'EUR'
+ self.currencies_without_fractions = %w(BGN HRK DKK NOK GBP PLN CZK RON SEK CHF HUF)
+ self.money_format = :cents
+ self.supported_cardtypes = %i[visa master maestro american_express jcb]
+
+ ENDPOINTS_MAPPING = {
+ purchase: 'orders/2steps/payment',
+ authorize: 'orders/2steps/init',
+ capture: 'operations/{%s}/captures',
+ verify: 'orders/card_verification',
+ void: 'operations/{%s}/cancels',
+ refund: 'operations/{%s}/refunds'
+ }
+
+ def initialize(options = {})
+ requires!(options, :api_key)
+ @api_key = options[:api_key]
+ super
+ end
+
+ def purchase(amount, payment_method, options = {})
+ post = {}
+ commit('purchase', post, options)
+ end
+
+ def authorize(amount, payment_method, options = {})
+ post = {}
+ add_auth_purchase(post, amount, payment_method, options)
+ commit('authorize', post, options)
+ end
+
+ def capture(amount, authorization, options = {})
+ post = {}
+ commit('capture', post, options)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ commit('void', post, options)
+ end
+
+ def refund(amount, authorization, options = {})
+ post = {}
+ commit('refund', post, options)
+ end
+
+ def verify(credit_card, options = {})
+ post = {}
+ commit('verify', post, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript) end
+
+ private
+
+ def add_invoice(post, money, options) end
+
+ def add_payment_method(post, payment_method) end
+
+ def add_reference(post, authorization) end
+
+ def add_auth_purchase(post, money, payment, options) end
+
+ def commit(action, params, options)
+ url = build_request_url(action)
+ response = ssl_post(url, params.to_json, request_headers(options))
+
+ unless response
+ Response.new(
+ success_from(response),
+ message_from(success_from(response), response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['some_avs_result_key']),
+ cvv_result: CVVResult.new(response['some_cvv_result_key']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+ rescue ResponseError => e
+ response = e.response.body
+ JSON.parse(response)
+ end
+
+ def request_headers(options)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'X-Api-Key' => @api_key,
+ 'Correlation-Id' => options.dig(:order, :order_id) || SecureRandom.uuid
+ }
+ headers
+ end
+
+ def build_request_url(action, id = nil)
+ base_url = test? ? test_url : live_url
+ endpoint = ENDPOINTS_MAPPING[action.to_sym] % id
+ base_url + endpoint
+ end
+
+ def success_from(response)
+ response == 'SUCCESS'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response.dig('errors') unless response
+ end
+ end
+
+ def authorization_from(response)
+ response.dig('latest_payment_attempt', 'payment_intent_id') unless response
+ end
+
+ def error_code_from(response)
+ response['provider_original_response_code'] || response['code'] unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 4f4e225cc5b..43c848a643a 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -1478,3 +1478,6 @@ worldpay_us:
acctid: MPNAB
subid: SPREE
merchantpin: "1234567890"
+
+x_pay:
+ api_key: 2d708950-50a1-434e-9a93-5d3ae2f1dd9f
diff --git a/test/remote/gateways/remote_xpay_test.rb b/test/remote/gateways/remote_xpay_test.rb
new file mode 100644
index 00000000000..92ec7c33ab5
--- /dev/null
+++ b/test/remote/gateways/remote_xpay_test.rb
@@ -0,0 +1,15 @@
+require 'test_helper'
+
+class RemoteRapydTest < Test::Unit::TestCase
+ def setup
+ @gateway = XpayGateway.new(fixtures(:x_pay))
+ @amount = 200
+ @credit_card = credit_card('4111111111111111')
+ @options = {}
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response
+ end
+end
diff --git a/test/unit/gateways/xpay_test.rb b/test/unit/gateways/xpay_test.rb
new file mode 100644
index 00000000000..318e88d3de7
--- /dev/null
+++ b/test/unit/gateways/xpay_test.rb
@@ -0,0 +1,57 @@
+require 'test_helper'
+
+class XpayTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = XpayGateway.new(
+ api_key: 'some api key'
+ )
+ @credit_card = credit_card
+ @amount = 100
+ @base_url = @gateway.test_url
+ @options = {}
+ end
+
+ def test_supported_countries
+ assert_equal %w(AT BE CY EE FI FR DE GR IE IT LV LT LU MT PT SK SI ES BG HR DK NO PL RO RO SE CH HU), XpayGateway.supported_countries
+ end
+
+ def test_supported_cardtypes
+ assert_equal %i[visa master maestro american_express jcb], @gateway.supported_cardtypes
+ end
+
+ def test_build_request_url_for_purchase
+ action = :purchase
+ assert_equal @gateway.send(:build_request_url, action), "#{@base_url}orders/2steps/payment"
+ end
+
+ def test_build_request_url_with_id_param
+ action = :refund
+ id = 123
+ assert_equal @gateway.send(:build_request_url, action, id), "#{@base_url}operations/{123}/refunds"
+ end
+
+ def test_invalid_instance
+ assert_raise ArgumentError do
+ XpayGateway.new()
+ end
+ end
+
+ def test_check_request_headers
+ stub_comms do
+ @gateway.send(:commit, 'purchase', {}, {})
+ end.check_request(skip_response: true) do |_endpoint, _data, headers|
+ assert_equal headers['Content-Type'], 'application/json'
+ assert_equal headers['X-Api-Key'], 'some api key'
+ end
+ end
+
+ def test_check_authorize_endpoint
+ stub_comms do
+ @gateway.send(:authorize, @amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |endpoint, _data, _headers|
+ assert_match(/orders\/2steps\/init/, endpoint)
+ end
+ end
+end
From 9f4eb4b825970cacd4a1f3d77b540843a36f35bd Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Thu, 21 Sep 2023 10:50:03 -0500
Subject: [PATCH 168/390] Rapyd: Add validation to not send cvv and
network_reference_id (#4895)
Description
-------------------------
[SER-826](https://spreedly.atlassian.net/browse/SER-826)
This commit add some validations to not send the cvv and network_reference_id
on recurring transactions if it does not exist
Unit test
-------------------------
Finished in 0.132965 seconds.
34 tests, 163 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
Finished in 79.856446 seconds.
40 tests, 115 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.5% passed
Rubocop
-------------------------
766 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 21 ++++---
test/remote/gateways/remote_rapyd_test.rb | 15 ++++-
test/unit/gateways/rapyd_test.rb | 59 +++++++++++++++++++
4 files changed, 88 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 2b271ce62f8..166a7091fd1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,7 @@
* Rapyd: fixing issue with json encoding and signatures [Heavyblade] #4892
* SumUp: Setup, Scrub and Purchase build [sinourain] #4890
* XpayGateway: Initial setup [javierpedrozaing] #4889
+* Rapyd: Add validation to not send cvv and network_reference_id [javierpedrozaing] #4895
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 649a40df862..9465b506ab1 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -146,10 +146,10 @@ def add_stored_credential(post, options)
end
def add_network_reference_id(post, options)
- return unless options[:stored_credential] || options[:network_transaction_id]
+ return unless (options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring') || options[:network_transaction_id]
network_transaction_id = options[:network_transaction_id] || options[:stored_credential][:network_transaction_id]
- post[:payment_method][:fields][:network_reference_id] = network_transaction_id if network_transaction_id
+ post[:payment_method][:fields][:network_reference_id] = network_transaction_id unless network_transaction_id&.empty?
end
def add_initiation_type(post, options)
@@ -169,11 +169,18 @@ def add_creditcard(post, payment, options)
pm_fields[:expiration_month] = payment.month.to_s
pm_fields[:expiration_year] = payment.year.to_s
pm_fields[:name] = "#{payment.first_name} #{payment.last_name}"
- pm_fields[:cvv] = payment.verification_value.to_s if payment.verification_value.present?
-
+ pm_fields[:cvv] = payment.verification_value.to_s unless send_cvv_value?(payment, options)
add_stored_credential(post, options)
end
+ def send_cvv_value?(payment, options)
+ payment.verification_value.nil? || payment.verification_value&.empty? || (options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring')
+ end
+
+ def send_customer_object?(options)
+ options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring'
+ end
+
def add_ach(post, payment, options)
post[:payment_method] = {}
post[:payment_method][:fields] = {}
@@ -192,7 +199,7 @@ def add_tokens(post, payment, options)
customer_id, card_id = payment.split('|')
- post[:customer] = customer_id
+ post[:customer] = customer_id unless send_customer_object?(options)
post[:payment_method] = card_id
end
@@ -237,14 +244,14 @@ def add_payment_urls(post, options, action = '')
def add_customer_data(post, payment, options, action = '')
phone_number = options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
post[:phone_number] = phone_number.gsub(/\D/, '') unless phone_number.nil?
- post[:email] = options[:email]
+ post[:email] = options[:email] unless send_customer_object?(options)
return if payment.is_a?(String)
return add_customer_id(post, options) if options[:customer_id]
if action == 'store'
post.merge!(customer_fields(payment, options))
else
- post[:customer] = customer_fields(payment, options)
+ post[:customer] = customer_fields(payment, options) unless send_customer_object?(options)
end
end
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index c98be44be88..1b13676a808 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -381,10 +381,23 @@ def test_successful_authorize_with_execute_threed
end
def test_successful_purchase_without_cvv
- options = @options.merge({ pm_type: 'gb_visa_card', stored_credential: { network_transaction_id: rand.to_s[2..11] } })
+ options = @options.merge({ pm_type: 'gb_visa_card', network_transaction_id: rand.to_s[2..11] })
@credit_card.verification_value = nil
response = @gateway.purchase(100, @credit_card, options)
assert_success response
assert_equal 'SUCCESS', response.message
end
+
+ def test_successful_recurring_transaction_without_cvv
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_empty_network_transaction_id
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: '', initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 4bed0c08f73..fddb713a062 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -378,6 +378,65 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_not_send_cvv_with_empty_value
+ @credit_card.verification_value = ''
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['payment_method']['fields']['cvv']
+ end
+ end
+
+ def test_not_send_cvv_with_nil_value
+ @credit_card.verification_value = nil
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['payment_method']['fields']['cvv']
+ end
+ end
+
+ def test_not_send_cvv_for_recurring_transactions
+ @options[:stored_credential] = {
+ reason_type: 'recurring',
+ network_transaction_id: '12345'
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['payment_method']['fields']['cvv']
+ end
+ end
+
+ def test_not_send_network_reference_id_for_recurring_transactions
+ @options[:stored_credential] = {
+ reason_type: 'recurring',
+ network_transaction_id: nil
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['payment_method']['fields']['network_reference_id']
+ end
+ end
+
+ def test_not_send_customer_object_for_recurring_transactions
+ @options[:stored_credential] = {
+ reason_type: 'recurring',
+ network_transaction_id: '12345'
+ }
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['customer']
+ end
+ end
+
private
def pre_scrubbed
From 78be05be6676a10659128d89105f55423fb8ec33 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 19 Sep 2023 11:51:42 -0500
Subject: [PATCH 169/390] Ebanx: Add Ecuador & Bolivia in supported countries
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ebanx.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 166a7091fd1..a660431f447 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@
* SumUp: Setup, Scrub and Purchase build [sinourain] #4890
* XpayGateway: Initial setup [javierpedrozaing] #4889
* Rapyd: Add validation to not send cvv and network_reference_id [javierpedrozaing] #4895
+* Ebanx: Add Ecuador and Bolivia as supported countries [almalee24] #4893
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index d9233cc0841..4588eddb7f7 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -4,7 +4,7 @@ class EbanxGateway < Gateway
self.test_url = 'https://sandbox.ebanxpay.com/ws/'
self.live_url = 'https://api.ebanxpay.com/ws/'
- self.supported_countries = %w(BR MX CO CL AR PE)
+ self.supported_countries = %w(BR MX CO CL AR PE BO EC)
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa master american_express discover diners_club elo hipercard]
From 2e75e25dd5654448a14652646989f831a1377b8d Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Mon, 25 Sep 2023 09:19:36 -0500
Subject: [PATCH 170/390] Rapyd: Fix cvv validation (#4896)
* Rapyd: Fix cvv validation
Description
-------------------------
This is a super small commit to update the validations to not send cvv value
Unit test
-------------------------
Finished in 0.132965 seconds.
34 tests, 163 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
Finished in 79.856446 seconds.
40 tests, 115 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.5% passed
Rubocop
-------------------------
766 files inspected, no offenses detected
* Improve logic to check for valid ntid when deciding if we should send the
* Use .blank? to check for cvv and validate presence of ntid before cvv check
---------
Co-authored-by: Javier Pedroza
Co-authored-by: naashton
---
lib/active_merchant/billing/gateways/rapyd.rb | 11 ++++++-----
test/remote/gateways/remote_rapyd_test.rb | 6 ++++++
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 9465b506ab1..6082c02ca39 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -169,18 +169,19 @@ def add_creditcard(post, payment, options)
pm_fields[:expiration_month] = payment.month.to_s
pm_fields[:expiration_year] = payment.year.to_s
pm_fields[:name] = "#{payment.first_name} #{payment.last_name}"
- pm_fields[:cvv] = payment.verification_value.to_s unless send_cvv_value?(payment, options)
+ pm_fields[:cvv] = payment.verification_value.to_s unless valid_network_transaction_id?(options) || payment.verification_value.blank?
add_stored_credential(post, options)
end
- def send_cvv_value?(payment, options)
- payment.verification_value.nil? || payment.verification_value&.empty? || (options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring')
- end
-
def send_customer_object?(options)
options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring'
end
+ def valid_network_transaction_id?(options)
+ network_transaction_id = options[:network_tansaction_id] || options.dig(:stored_credential_options, :network_transaction_id) || options.dig(:stored_credential, :network_transaction_id)
+ return network_transaction_id.present?
+ end
+
def add_ach(post, payment, options)
post[:payment_method] = {}
post[:payment_method][:fields] = {}
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 1b13676a808..273b352bfe3 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -400,4 +400,10 @@ def test_successful_purchase_empty_network_transaction_id
assert_success response
assert_equal 'SUCCESS', response.message
end
+
+ def test_successful_purchase_nil_network_transaction_id
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: nil, initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
From fab678b23fb8860568c9ef4e7eb4ca7a68f7c65b Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 22 Aug 2023 13:30:47 -0500
Subject: [PATCH 171/390] Decidir: Add support for network tokens
This PR adds support for network tokens to the Decidir gateway.
---
CHANGELOG | 1 +
.../billing/gateways/decidir.rb | 58 ++++++++++----
test/remote/gateways/remote_decidir_test.rb | 46 +++++++++--
test/unit/gateways/decidir_test.rb | 76 +++++++++++++++++++
4 files changed, 160 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a660431f447..a16b4fc434a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@
* XpayGateway: Initial setup [javierpedrozaing] #4889
* Rapyd: Add validation to not send cvv and network_reference_id [javierpedrozaing] #4895
* Ebanx: Add Ecuador and Bolivia as supported countries [almalee24] #4893
+* Decidir: Add support for network tokens [almalee24] #4870
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/decidir.rb b/lib/active_merchant/billing/gateways/decidir.rb
index 38ce71dbab3..e06ce8da3fb 100644
--- a/lib/active_merchant/billing/gateways/decidir.rb
+++ b/lib/active_merchant/billing/gateways/decidir.rb
@@ -167,29 +167,55 @@ def add_amount(post, money, options)
post[:amount] = localized_amount(money, currency).to_i
end
- def add_payment(post, credit_card, options)
- card_data = {}
+ def add_payment(post, payment_method, options)
+ add_common_payment_data(post, payment_method, options)
+
+ case payment_method
+ when NetworkTokenizationCreditCard
+ add_network_token(post, payment_method, options)
+ else
+ add_credit_card(post, payment_method, options)
+ end
+ end
+
+ def add_common_payment_data(post, payment_method, options)
+ post[:card_data] = {}
+
+ data = post[:card_data]
+ data[:card_holder_identification] = {}
+ data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
+ data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
+ data[:card_holder_name] = payment_method.name if payment_method.name
+
+ # additional data used for Visa transactions
+ data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
+ data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
+ end
+
+ def add_network_token(post, payment_method, options)
+ post[:is_tokenized_payment] = true
+ post[:fraud_detection] ||= {}
+ post[:fraud_detection][:sent_to_cs] = false
+ post[:card_data][:last_four_digits] = options[:last_4]
+
+ post[:token_card_data] = {
+ token: payment_method.number,
+ eci: payment_method.eci,
+ cryptogram: payment_method.payment_cryptogram
+ }
+ end
+
+ def add_credit_card(post, credit_card, options)
+ card_data = post[:card_data]
card_data[:card_number] = credit_card.number
card_data[:card_expiration_month] = format(credit_card.month, :two_digits)
card_data[:card_expiration_year] = format(credit_card.year, :two_digits)
card_data[:security_code] = credit_card.verification_value if credit_card.verification_value?
- card_data[:card_holder_name] = credit_card.name if credit_card.name
# the device_unique_id has to be sent in via the card data (as device_unique_identifier) no other fraud detection fields require this
- if options[:fraud_detection].present?
- card_data[:fraud_detection] = {} if (options[:fraud_detection][:device_unique_id]).present?
- card_data[:fraud_detection][:device_unique_identifier] = (options[:fraud_detection][:device_unique_id]) if (options[:fraud_detection][:device_unique_id]).present?
+ if (device_id = options.dig(:fraud_detection, :device_unique_id))
+ card_data[:fraud_detection] = { device_unique_identifier: device_id }
end
-
- # additional data used for Visa transactions
- card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
- card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
-
- card_data[:card_holder_identification] = {}
- card_data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
- card_data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
-
- post[:card_data] = card_data
end
def add_aggregate_data(post, options)
diff --git a/test/remote/gateways/remote_decidir_test.rb b/test/remote/gateways/remote_decidir_test.rb
index 7e590f2fd00..22831af2ed7 100644
--- a/test/remote/gateways/remote_decidir_test.rb
+++ b/test/remote/gateways/remote_decidir_test.rb
@@ -30,6 +30,16 @@ def setup
amount: 1500
}
]
+ @network_token = network_tokenization_credit_card(
+ '4012001037141112',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: '000203016912340000000FA08400317500000000',
+ name: 'Tesest payway'
+ )
+
+ @failed_message = ['PEDIR AUTORIZACION | request_authorization_card', 'COMERCIO INVALIDO | invalid_card']
+ @failed_code = ['1, call_issuer', '3, config_error']
end
def test_successful_purchase
@@ -53,6 +63,22 @@ def test_successful_purchase_with_amex
assert response.authorization
end
+ def test_successful_purchase_with_network_token
+ options = {
+ card_holder_door_number: 1234,
+ card_holder_birthday: '200988',
+ card_holder_identification_type: 'DNI',
+ card_holder_identification_number: '44444444',
+ order_id: SecureRandom.uuid,
+ last_4: @credit_card.last_digits
+ }
+ response = @gateway_for_purchase.purchase(500, @network_token, options)
+
+ assert_success response
+ assert_equal 'approved', response.message
+ assert response.authorization
+ end
+
# This test is currently failing.
# Decidir hasn't been able to provide a valid Diners Club test card number.
#
@@ -169,9 +195,14 @@ def test_failed_purchase_with_bad_csmdds
def test_failed_purchase
response = @gateway_for_purchase.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'COMERCIO INVALIDO | invalid_card', response.message
- assert_equal '3, config_error', response.error_code
- assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ assert_equal @failed_message.include?(response.message), true
+ assert_equal @failed_code.include?(response.error_code), true
+
+ if response.error_code.start_with?('1')
+ assert_match Gateway::STANDARD_ERROR_CODE[:call_issuer], response.error_code
+ else
+ assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
end
def test_failed_purchase_with_invalid_field
@@ -196,8 +227,13 @@ def test_successful_authorize_and_capture
def test_failed_authorize
response = @gateway_for_auth.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'PEDIR AUTORIZACION | request_authorization_card', response.message
- assert_match '1, call_issuer', response.error_code
+ assert_equal @failed_message.include?(response.message), true
+ assert_equal @failed_code.include?(response.error_code), true
+ if response.error_code.start_with?('1')
+ assert_match Gateway::STANDARD_ERROR_CODE[:call_issuer], response.error_code
+ else
+ assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
end
def test_failed_partial_capture
diff --git a/test/unit/gateways/decidir_test.rb b/test/unit/gateways/decidir_test.rb
index 300d7d40974..3c82aada301 100644
--- a/test/unit/gateways/decidir_test.rb
+++ b/test/unit/gateways/decidir_test.rb
@@ -38,6 +38,13 @@ def setup
amount: 1500
}
]
+
+ @network_token = network_tokenization_credit_card(
+ '4012001037141112',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: '000203016912340000000FA08400317500000000'
+ )
end
def test_successful_purchase
@@ -378,6 +385,22 @@ def test_successful_inquire_with_authorization
assert response.test?
end
+ def test_network_token_payment_method
+ options = {
+ card_holder_name: 'Tesest payway',
+ card_holder_door_number: 1234,
+ card_holder_birthday: '200988',
+ card_holder_identification_type: 'DNI',
+ card_holder_identification_number: '44444444',
+ last_4: @credit_card.last_digits
+ }
+ @gateway_for_auth.expects(:ssl_request).returns(successful_network_token_response)
+ response = @gateway_for_auth.authorize(100, @network_token, options)
+
+ assert_success response
+ assert_equal 49120515, response.authorization
+ end
+
def test_scrub
assert @gateway_for_purchase.supports_scrubbing?
assert_equal @gateway_for_purchase.scrub(pre_scrubbed), post_scrubbed
@@ -549,6 +572,59 @@ def failed_authorize_response
)
end
+ def successful_network_token_response
+ %(
+ {"id": 49120515,
+ "site_transaction_id": "Tx1673372774",
+ "payment_method_id": 1,
+ "card_brand": "Visa",
+ "amount": 1200,
+ "currency": "ars",
+ "status": "approved",
+ "status_details": {
+ "ticket": "88",
+ "card_authorization_code": "B45857",
+ "address_validation_code": "VTE2222",
+ "error": null
+ },
+ "date": "2023-01-10T14:46Z",
+ "customer": null,
+ "bin": "450799",
+ "installments": 1,
+ "first_installment_expiration_date": null,
+ "payment_type": "single",
+ "sub_payments": [],
+ "site_id": "09001000",
+ "fraud_detection": null,
+ "aggregate_data": {
+ "indicator": "1",
+ "identification_number": "30598910045",
+ "bill_to_pay": "Payway_Test",
+ "bill_to_refund": "Payway_Test",
+ "merchant_name": "PAYWAY",
+ "street": "Lavarden",
+ "number": "247",
+ "postal_code": "C1437FBE",
+ "category": "05044",
+ "channel": "005",
+ "geographic_code": "C1437",
+ "city": "Buenos Aires",
+ "merchant_id": "id_Aggregator",
+ "province": "Buenos Aires",
+ "country": "Argentina",
+ "merchant_email": "qa@test.com",
+ "merchant_phone": "+541135211111"
+ },
+ "establishment_name": null,
+ "spv":null,
+ "confirmed":null,
+ "bread":null,
+ "customer_token":null,
+ "card_data":"/tokens/49120515",
+ "token":"b7b6ca89-ed81-44e0-9d1f-3b3cf443cd74"}
+ )
+ end
+
def successful_capture_response
%(
{"id":7720214,"site_transaction_id":"0fcedc95-4fbc-4299-80dc-f77e9dd7f525","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"approved","status_details":{"ticket":"8187","card_authorization_code":"180548","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T18:05Z","customer":null,"bin":"450799","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":{"id":78436,"origin_amount":100,"date":"2019-06-21T03:00Z"},"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7720214"}
From 257075c25af2bdfc680cb5c4e3a501b5961b480a Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Tue, 12 Sep 2023 10:33:30 -0400
Subject: [PATCH 172/390] Braintree: return global_id in response
CER-868
This field needs to be added to the transaction_hash method to
be returned in the response. This also updates the Braintree gem, which was need to make these fields available
Remote
107 tests, 212 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
31.7757% passed
Unit
95 tests, 110 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
41.0526% passed
Local
5599 tests, 77770 assertions, 0 failures, 56 errors, 0 pendings, 0 omissions, 0 notifications
98.9998% passed
---
Gemfile | 2 +-
.../billing/gateways/braintree_blue.rb | 11 ++++++++++-
test/remote/gateways/remote_braintree_blue_test.rb | 7 +++++++
3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/Gemfile b/Gemfile
index f24e5c86218..5f3d4397223 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,7 @@ gem 'rubocop', '~> 0.72.0', require: false
group :test, :remote_test do
# gateway-specific dependencies, keeping these gems out of the gemspec
- gem 'braintree', '>= 4.12.0'
+ gem 'braintree', '>= 4.14.0'
gem 'jose', '~> 1.1.3'
gem 'jwe'
gem 'mechanize'
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index cb49c767190..91a27f00ec1 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -618,6 +618,14 @@ def transaction_hash(result)
risk_data = nil
end
+ if transaction.payment_receipt
+ payment_receipt = {
+ 'global_id' => transaction.payment_receipt.global_id
+ }
+ else
+ payment_receipt = nil
+ end
+
{
'order_id' => transaction.order_id,
'amount' => transaction.amount.to_s,
@@ -632,7 +640,8 @@ def transaction_hash(result)
'network_transaction_id' => transaction.network_transaction_id || nil,
'processor_response_code' => response_code_from_result(result),
'processor_authorization_code' => transaction.processor_authorization_code,
- 'recurring' => transaction.recurring
+ 'recurring' => transaction.recurring,
+ 'payment_receipt' => payment_receipt
}
end
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 4cfd400a67d..695315f7308 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -1238,6 +1238,13 @@ def test_successful_purchase_with_with_prepaid_debit_issuing_bank
assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['issuing_bank']
end
+ def test_successful_purchase_with_global_id
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['payment_receipt']['global_id']
+ end
+
def test_unsucessful_purchase_using_a_bank_account_token_not_verified
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
response = @gateway.store(bank_account, @options.merge(@check_required_options))
From 054c26cebb5d46e5fb63956baf794e1ead82ad66 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 22 Sep 2023 13:38:44 -0500
Subject: [PATCH 173/390] Element: Fix credit card name bug
Update bug that causes error is first_name and last_name are nil
Remote:
31 tests, 86 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
27 tests, 136 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/element.rb | 2 +-
test/unit/gateways/element_test.rb | 12 ++++++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index a16b4fc434a..a3506adeff0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,7 @@
* Rapyd: Add validation to not send cvv and network_reference_id [javierpedrozaing] #4895
* Ebanx: Add Ecuador and Bolivia as supported countries [almalee24] #4893
* Decidir: Add support for network tokens [almalee24] #4870
+* Element: Fix credit card name bug [almalee24] #4898
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb
index 3a833406430..0385db547d7 100644
--- a/lib/active_merchant/billing/gateways/element.rb
+++ b/lib/active_merchant/billing/gateways/element.rb
@@ -240,7 +240,7 @@ def add_credit_card(xml, payment)
xml.CardNumber payment.number
xml.ExpirationMonth format(payment.month, :two_digits)
xml.ExpirationYear format(payment.year, :two_digits)
- xml.CardholderName payment.first_name + ' ' + payment.last_name
+ xml.CardholderName "#{payment.first_name} #{payment.last_name}"
xml.CVV payment.verification_value
end
end
diff --git a/test/unit/gateways/element_test.rb b/test/unit/gateways/element_test.rb
index ece96d068e5..bf461d6704a 100644
--- a/test/unit/gateways/element_test.rb
+++ b/test/unit/gateways/element_test.rb
@@ -25,6 +25,18 @@ def test_successful_purchase
assert_equal '2005831886|100', response.authorization
end
+ def test_successful_purchase_without_name
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ @credit_card.first_name = nil
+ @credit_card.last_name = nil
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert_equal '2005831886|100', response.authorization
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_post).returns(failed_purchase_response)
From 19d0479e3c058d05b64da0e398944448791f6b9d Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 12 Sep 2023 10:27:18 -0500
Subject: [PATCH 174/390] Adyen: Add payout endpoint
Remote:
137 tests, 453 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.9708% passed
Unit:
111 tests, 585 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 133 +++++++++++++-----
test/remote/gateways/remote_adyen_test.rb | 54 ++++++-
test/unit/gateways/adyen_test.rb | 54 ++++++-
4 files changed, 204 insertions(+), 38 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a3506adeff0..637e1d66228 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,7 @@
* Ebanx: Add Ecuador and Bolivia as supported countries [almalee24] #4893
* Decidir: Add support for network tokens [almalee24] #4870
* Element: Fix credit card name bug [almalee24] #4898
+* Adyen: Add payout endpoint [almalee24] #4885
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 6bb97a036f3..e80ffb472e7 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -93,12 +93,27 @@ def refund(money, authorization, options = {})
end
def credit(money, payment, options = {})
- action = 'refundWithData'
+ action = options[:payout] ? 'payout' : 'refundWithData'
post = init_post(options)
add_invoice(post, money, options)
add_payment(post, payment, options, action)
add_shopper_reference(post, options)
add_network_transaction_reference(post, options)
+
+ if action == 'payout'
+ add_shopper_interaction(post, payment, options)
+ add_fraud_offset(post, options)
+ add_fund_source(post, options)
+ add_recurring_contract(post, options)
+
+ if (address = options[:billing_address] || options[:address]) && address[:country]
+ add_billing_address(post, address)
+ end
+
+ post[:dateOfBirth] = options[:date_of_birth] if options[:date_of_birth]
+ post[:nationality] = options[:nationality] if options[:nationality]
+ end
+
commit(action, post, options)
end
@@ -230,12 +245,28 @@ def scrub(transcript)
def add_extra_data(post, payment, options)
post[:telephoneNumber] = (options[:billing_address][:phone_number] if options.dig(:billing_address, :phone_number)) || (options[:billing_address][:phone] if options.dig(:billing_address, :phone)) || ''
- post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
post[:deliveryDate] = options[:delivery_date] if options[:delivery_date]
post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference]
post[:captureDelayHours] = options[:capture_delay_hours] if options[:capture_delay_hours]
+ post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
+ post[:shopperIP] = options[:shopper_ip] || options[:ip] if options[:shopper_ip] || options[:ip]
+ post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
+ post[:store] = options[:store] if options[:store]
+
+ add_additional_data(post, payment, options)
+ add_risk_data(post, options)
+ add_shopper_reference(post, options)
+ add_merchant_data(post, options)
+ add_fraud_offset(post, options)
+ end
+
+ def add_fraud_offset(post, options)
+ post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
+ end
+
+ def add_additional_data(post, payment, options)
post[:additionalData] ||= {}
post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand]
post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag]
@@ -244,12 +275,7 @@ def add_extra_data(post, payment, options)
post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data]
post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
- post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
- post[:store] = options[:store] if options[:store]
- add_shopper_data(post, options)
- add_risk_data(post, options)
- add_shopper_reference(post, options)
- add_merchant_data(post, options)
+ post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
end
def extract_and_transform(mapper, from)
@@ -372,15 +398,6 @@ def add_data_lodging(post, options)
post[:additionalData].compact!
end
- def add_shopper_data(post, options)
- post[:shopperEmail] = options[:email] if options[:email]
- post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
- post[:shopperIP] = options[:ip] if options[:ip]
- post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
- post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
- post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
- end
-
def add_shopper_statement(post, options)
return unless options[:shopper_statement]
@@ -484,16 +501,21 @@ def add_address(post, options)
return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash)
if (address = options[:billing_address] || options[:address]) && address[:country]
- post[:billingAddress] = {}
- post[:billingAddress][:street] = address[:address1] || 'NA'
- post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
- post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
- post[:billingAddress][:city] = address[:city] || 'NA'
- post[:billingAddress][:stateOrProvince] = get_state(address)
- post[:billingAddress][:country] = address[:country] if address[:country]
+ add_billing_address(post, address)
end
end
+ def add_billing_address(post, address)
+ post[:billingAddress] = {}
+ post[:billingAddress][:street] = address[:address1] || 'NA'
+ post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
+ post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
+ post[:billingAddress][:city] = address[:city] || 'NA'
+ post[:billingAddress][:stateOrProvince] = get_state(address)
+ post[:billingAddress][:country] = address[:country] if address[:country]
+ post[:telephoneNumber] = address[:phone_number] || address[:phone] || ''
+ end
+
def get_state(address)
address[:state] && !address[:state].blank? ? address[:state] : 'NA'
end
@@ -531,6 +553,7 @@ def add_payment(post, payment, options, action = nil)
end
def add_bank_account(post, bank_account, options, action)
+ add_shopper_data(post, bank_account, options)
bank = {
bankAccountNumber: bank_account.account_number,
ownerName: bank_account.name,
@@ -544,6 +567,7 @@ def add_bank_account(post, bank_account, options, action)
end
def add_card(post, credit_card)
+ add_shopper_data(post, credit_card, options)
card = {
expiryMonth: credit_card.month,
expiryYear: credit_card.year,
@@ -558,6 +582,14 @@ def add_card(post, credit_card)
post[:card] = card
end
+ def add_shopper_data(post, payment, options)
+ post[:shopperName] = {}
+ post[:shopperName][:firstName] = payment.first_name
+ post[:shopperName][:lastName] = payment.last_name
+ post[:shopperEmail] = options[:email] if options[:email]
+ post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
+ end
+
def capture_options(options)
return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]
@@ -589,11 +621,12 @@ def add_mpi_data_for_network_tokenization_card(post, payment, options)
def add_recurring_contract(post, options = {})
return unless options[:recurring_contract_type]
- recurring = {
- contract: options[:recurring_contract_type]
- }
-
- post[:recurring] = recurring
+ post[:recurring] = {}
+ post[:recurring][:contract] = options[:recurring_contract_type]
+ post[:recurring][:recurringDetailName] = options[:recurring_detail_name] if options[:recurring_detail_name]
+ post[:recurring][:recurringExpiry] = options[:recurring_expiry] if options[:recurring_expiry]
+ post[:recurring][:recurringFrequency] = options[:recurring_frequency] if options[:recurring_frequency]
+ post[:recurring][:tokenService] = options[:token_service] if options[:token_service]
end
def add_application_info(post, options)
@@ -689,6 +722,23 @@ def add_3ds2_authenticated_data(post, options)
}
end
+ def add_fund_source(post, options)
+ return unless fund_source = options[:fund_source]
+
+ post[:fundSource] = {}
+ post[:fundSource][:additionalData] = fund_source[:additional_data] if fund_source[:additional_data]
+
+ if fund_source[:first_name] && fund_source[:last_name]
+ post[:fundSource][:shopperName] = {}
+ post[:fundSource][:shopperName][:firstName] = fund_source[:first_name]
+ post[:fundSource][:shopperName][:lastName] = fund_source[:last_name]
+ end
+
+ if (address = fund_source[:billing_address])
+ add_billing_address(post[:fundSource], address)
+ end
+ end
+
def parse(body)
return {} if body.blank?
@@ -727,8 +777,14 @@ def cvv_result_from(response)
end
def endpoint(action)
- recurring = %w(disable storeToken).include?(action)
- recurring ? "Recurring/#{RECURRING_API_VERSION}/#{action}" : "Payment/#{PAYMENT_API_VERSION}/#{action}"
+ case action
+ when 'disable', 'storeToken'
+ "Recurring/#{RECURRING_API_VERSION}/#{action}"
+ when 'payout'
+ "Payout/#{PAYMENT_API_VERSION}/#{action}"
+ else
+ "Payment/#{PAYMENT_API_VERSION}/#{action}"
+ end
end
def url(action)
@@ -772,15 +828,24 @@ def success_from(action, response, options)
response['response'] == '[detail-successfully-disabled]'
when 'refundWithData'
response['resultCode'] == 'Received'
+ when 'payout'
+ return false unless response['resultCode'] && response['authCode']
+
+ %[AuthenticationFinished Authorised Received].include?(response['resultCode'])
else
false
end
end
def message_from(action, response, options = {})
- return authorize_message_from(response, options) if %w(authorise authorise3d authorise3ds2).include?(action.to_s)
-
- response['response'] || response['message'] || response['result'] || response['resultCode']
+ case action.to_s
+ when 'authorise', 'authorise3d', 'authorise3ds2'
+ authorize_message_from(response, options)
+ when 'payout'
+ response['refusalReason'] || response['resultCode'] || response['message']
+ else
+ response['response'] || response['message'] || response['result'] || response['resultCode']
+ end
end
def authorize_message_from(response, options = {})
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index 7567a872b26..8f772710cd7 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -128,13 +128,33 @@ def setup
verification_value: '737',
brand: 'visa'
)
+ @us_address = {
+ address1: 'Brannan Street',
+ address2: '121',
+ country: 'US',
+ city: 'Beverly Hills',
+ state: 'CA',
+ zip: '90210'
+ }
+
+ @payout_options = {
+ reference: 'P9999999999999999',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: @us_address,
+ nationality: 'NL',
+ order_id: 'P9999999999999999',
+ date_of_birth: '1990-01-01',
+ payout: true
+ }
@options = {
reference: '345123',
email: 'john.smith@test.com',
ip: '77.110.174.153',
shopper_reference: 'John Smith',
- billing_address: address(country: 'US', state: 'CA'),
+ billing_address: @us_address,
order_id: '123',
stored_credential: { reason_type: 'unscheduled' }
}
@@ -152,7 +172,7 @@ def setup
notification_url: 'https://example.com/notification',
browser_info: {
accept_header: 'unknown',
- depth: 100,
+ depth: 48,
java: false,
language: 'US',
height: 1000,
@@ -755,6 +775,36 @@ def test_failed_credit
assert_equal "Required field 'reference' is not provided.", response.message
end
+ def test_successful_payout_with_credit_card
+ response = @gateway.credit(2500, @credit_card, @payout_options)
+
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_successful_payout_with_fund_source
+ fund_source = {
+ fund_source: {
+ additional_data: { fundingSource: 'Debit' },
+ first_name: 'Payer',
+ last_name: 'Name',
+ billing_address: @us_address
+ }
+ }
+
+ response = @gateway.credit(2500, @credit_card, @payout_options.merge!(fund_source))
+
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_failed_payout_with_credit_card
+ response = @gateway.credit(2500, @credit_card, @payout_options.except(:date_of_birth))
+
+ assert_failure response
+ assert_equal 'Payout has been refused due to regulatory reasons', response.message
+ end
+
def test_successful_void
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 7978bbacba0..cab4ab63910 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -796,6 +796,29 @@ def test_successful_credit
assert_success response
end
+ def test_successful_payout_with_credit_card
+ payout_options = {
+ reference: 'P9999999999999999',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: @us_address,
+ nationality: 'NL',
+ order_id: 'P9999999999999999',
+ date_of_birth: '1990-01-01',
+ payout: true
+ }
+
+ stub_comms do
+ @gateway.credit(2500, @credit_card, payout_options)
+ end.check_request do |endpoint, data, _headers|
+ assert_match(/payout/, endpoint)
+ assert_match(/"dateOfBirth\":\"1990-01-01\"/, data)
+ assert_match(/"nationality\":\"NL\"/, data)
+ assert_match(/"shopperName\":{\"firstName\":\"Test\",\"lastName\":\"Card\"}/, data)
+ end.respond_with(successful_payout_response)
+ end
+
def test_successful_void
@gateway.expects(:ssl_post).returns(successful_void_response)
response = @gateway.void('7914775043909934')
@@ -1000,14 +1023,16 @@ def test_scrub_network_tokenization_card
def test_shopper_data
post = { card: { billingAddress: {} } }
- @gateway.send(:add_shopper_data, post, @options)
+ @gateway.send(:add_shopper_data, post, @credit_card, @options)
+ @gateway.send(:add_extra_data, post, @credit_card, @options)
assert_equal 'john.smith@test.com', post[:shopperEmail]
assert_equal '77.110.174.153', post[:shopperIP]
end
def test_shopper_data_backwards_compatibility
post = { card: { billingAddress: {} } }
- @gateway.send(:add_shopper_data, post, @options_shopper_data)
+ @gateway.send(:add_shopper_data, post, @credit_card, @options_shopper_data)
+ @gateway.send(:add_extra_data, post, @credit_card, @options_shopper_data)
assert_equal 'john2.smith@test.com', post[:shopperEmail]
assert_equal '192.168.100.100', post[:shopperIP]
end
@@ -1863,6 +1888,31 @@ def successful_credit_response
RESPONSE
end
+ def successful_payout_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "liabilityShift": "false",
+ "authCode": "081439",
+ "avsResult": "0 Unknown",
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
+ "threeDOffered": "false",
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
+ "authorisationMid": "50",
+ "acquirerAccountCode": "TestPmmAcquirerAccount",
+ "cvcResult": "0 Unknown",
+ "retry.attempt1.responseCode": "Approved",
+ "threeDAuthenticated": "false",
+ "retry.attempt1.rawResponse": "AUTHORISED"
+ },
+ "pspReference": "GMTN2VTQGJHKGK82",
+ "resultCode": "Authorised",
+ "authCode": "081439"
+ }
+ RESPONSE
+ end
+
def failed_credit_response
<<-RESPONSE
{
From e9e00a3ae15c38ec425681e88a4558cc874b6e2e Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Thu, 17 Aug 2023 16:32:48 -0400
Subject: [PATCH 175/390] Checkout: Add support for `sender`, `destination` and
`instruction` objects
CER-757
These changes will enhance functionality for Credits/Payouts. The fields that are added with this logic are all opt-in, no existing logic has been changed.
There are several fields that should be added conditionally, for this it would be best to reference the Checkout docs.
Remote Tests:
96 tests, 231 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
61 tests, 370 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Local Tests:
5551 tests, 77653 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.../billing/gateways/checkout_v2.rb | 97 +++++++++++++++-
.../gateways/remote_checkout_v2_test.rb | 65 +++++++++++
test/unit/gateways/checkout_v2_test.rb | 107 ++++++++++++++++++
3 files changed, 264 insertions(+), 5 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 13f6f7e757f..dc990d1fb52 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -57,12 +57,13 @@ def capture(amount, authorization, options = {})
def credit(amount, payment, options = {})
post = {}
- post[:instruction] = {}
- post[:instruction][:funds_transfer_type] = options[:funds_transfer_type] || 'FD'
add_processing_channel(post, options)
add_invoice(post, amount, options)
add_payment_method(post, payment, options, :destination)
add_source(post, options)
+ add_instruction_data(post, options)
+ add_payout_sender_data(post, options)
+ add_payout_destination_data(post, options)
commit(:credit, post, options)
end
@@ -173,6 +174,7 @@ def add_metadata(post, options, payment_method = nil)
end
def add_payment_method(post, payment_method, options, key = :source)
+ # the key = :destination when this method is called in def credit
post[key] = {}
case payment_method
when NetworkTokenizationCreditCard
@@ -190,13 +192,23 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
- post[key][:cvv] = payment_method.verification_value
+ post[key][:cvv] = payment_method.verification_value unless options[:funds_transfer_type]
post[key][:stored] = 'true' if options[:card_on_file] == true
+
+ # because of the way the key = is implemented in the method signature, some of the destination
+ # data will be added here, some in the destination specific method below.
+ # at first i was going to move this, but since this data is coming from the payment method
+ # i think it makes sense to leave it
if options[:account_holder_type]
post[key][:account_holder] = {}
post[key][:account_holder][:type] = options[:account_holder_type]
- post[key][:account_holder][:first_name] = payment_method.first_name if payment_method.first_name
- post[key][:account_holder][:last_name] = payment_method.last_name if payment_method.last_name
+
+ if options[:account_holder_type] == 'corporate' || options[:account_holder_type] == 'government'
+ post[key][:account_holder][:company_name] = payment_method.name if payment_method.respond_to?(:name)
+ else
+ post[key][:account_holder][:first_name] = payment_method.first_name if payment_method.first_name
+ post[key][:account_holder][:last_name] = payment_method.last_name if payment_method.last_name
+ end
else
post[key][:first_name] = payment_method.first_name if payment_method.first_name
post[key][:last_name] = payment_method.last_name if payment_method.last_name
@@ -321,6 +333,81 @@ def add_processing_channel(post, options)
post[:processing_channel_id] = options[:processing_channel_id] if options[:processing_channel_id]
end
+ def add_instruction_data(post, options)
+ post[:instruction] = {}
+ post[:instruction][:funds_transfer_type] = options[:funds_transfer_type] || 'FD'
+ post[:instruction][:purpose] = options[:instruction_purpose] if options[:instruction_purpose]
+ end
+
+ def add_payout_sender_data(post, options)
+ return unless options[:payout] == true
+
+ post[:sender] = {
+ # options for type are individual, corporate, or government
+ type: options[:sender][:type],
+ # first and last name required if sent by type: individual
+ first_name: options[:sender][:first_name],
+ middle_name: options[:sender][:middle_name],
+ last_name: options[:sender][:last_name],
+ # company name required if sent by type: corporate or government
+ company_name: options[:sender][:company_name],
+ # these are required fields for payout, may not work if address is blank or different than cardholder(option for sender to be a company or government).
+ # may need to still include in GSF hash.
+
+ address: {
+ address_line1: options.dig(:sender, :address, :address1),
+ address_line2: options.dig(:sender, :address, :address2),
+ city: options.dig(:sender, :address, :city),
+ state: options.dig(:sender, :address, :state),
+ country: options.dig(:sender, :address, :country),
+ zip: options.dig(:sender, :address, :zip)
+ }.compact,
+ reference: options[:sender][:reference],
+ reference_type: options[:sender][:reference_type],
+ source_of_funds: options[:sender][:source_of_funds],
+ # identification object is conditional. required when card metadata issuer_country = AR, BR, CO, or PR
+ # checkout docs say PR (Peru), but PR is puerto rico and PE is Peru so yikes
+ identification: {
+ type: options.dig(:sender, :identification, :type),
+ number: options.dig(:sender, :identification, :number),
+ issuing_country: options.dig(:sender, :identification, :issuing_country),
+ date_of_expiry: options.dig(:sender, :identification, :date_of_expiry)
+ }.compact,
+ date_of_birth: options[:sender][:date_of_birth],
+ country_of_birth: options[:sender][:country_of_birth],
+ nationality: options[:sender][:nationality]
+ }.compact
+ end
+
+ def add_payout_destination_data(post, options)
+ return unless options[:payout] == true
+
+ post[:destination] ||= {}
+ post[:destination][:account_holder] ||= {}
+ post[:destination][:account_holder][:email] = options[:destination][:account_holder][:email] if options[:destination][:account_holder][:email]
+ post[:destination][:account_holder][:date_of_birth] = options[:destination][:account_holder][:date_of_birth] if options[:destination][:account_holder][:date_of_birth]
+ post[:destination][:account_holder][:country_of_birth] = options[:destination][:account_holder][:country_of_birth] if options[:destination][:account_holder][:country_of_birth]
+ # below fields only required during a card to card payout
+ post[:destination][:account_holder][:phone] = {}
+ post[:destination][:account_holder][:phone][:country_code] = options.dig(:destination, :account_holder, :phone, :country_code) if options.dig(:destination, :account_holder, :phone, :country_code)
+ post[:destination][:account_holder][:phone][:number] = options.dig(:destination, :account_holder, :phone, :number) if options.dig(:destination, :account_holder, :phone, :number)
+
+ post[:destination][:account_holder][:identification] = {}
+ post[:destination][:account_holder][:identification][:type] = options.dig(:destination, :account_holder, :identification, :type) if options.dig(:destination, :account_holder, :identification, :type)
+ post[:destination][:account_holder][:identification][:number] = options.dig(:destination, :account_holder, :identification, :number) if options.dig(:destination, :account_holder, :identification, :number)
+ post[:destination][:account_holder][:identification][:issuing_country] = options.dig(:destination, :account_holder, :identification, :issuing_country) if options.dig(:destination, :account_holder, :identification, :issuing_country)
+ post[:destination][:account_holder][:identification][:date_of_expiry] = options.dig(:destination, :account_holder, :identification, :date_of_expiry) if options.dig(:destination, :account_holder, :identification, :date_of_expiry)
+
+ address = options[:billing_address] || options[:address] # destination address will come from the tokenized card billing address
+ post[:destination][:account_holder][:billing_address] = {}
+ post[:destination][:account_holder][:billing_address][:address_line1] = address[:address1] unless address[:address1].blank?
+ post[:destination][:account_holder][:billing_address][:address_line2] = address[:address2] unless address[:address2].blank?
+ post[:destination][:account_holder][:billing_address][:city] = address[:city] unless address[:city].blank?
+ post[:destination][:account_holder][:billing_address][:state] = address[:state] unless address[:state].blank?
+ post[:destination][:account_holder][:billing_address][:country] = address[:country] unless address[:country].blank?
+ post[:destination][:account_holder][:billing_address][:zip] = address[:zip] unless address[:zip].blank?
+ end
+
def add_marketplace_data(post, options)
if options[:marketplace]
post[:marketplace] = {}
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index e8b1d0c1e90..7361eecea9d 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -116,6 +116,48 @@ def setup
phone_country_code: '1',
phone: '9108675309'
)
+ @payout_options = @options.merge(
+ source_type: 'currency_account',
+ source_id: 'ca_spwmped4qmqenai7hcghquqle4',
+ funds_transfer_type: 'FD',
+ instruction_purpose: 'leisure',
+ destination: {
+ account_holder: {
+ phone: {
+ number: '9108675309',
+ country_code: '1'
+ },
+ identification: {
+ type: 'passport',
+ number: '12345788848438'
+ }
+ }
+ },
+ currency: 'GBP',
+ sender: {
+ type: 'individual',
+ first_name: 'Jane',
+ middle_name: 'Middle',
+ last_name: 'Doe',
+ address: {
+ address1: '123 Main St',
+ address2: 'Apt G',
+ city: 'Narnia',
+ state: 'ME',
+ zip: '12345',
+ country: 'US'
+ },
+ reference: '012345',
+ reference_type: 'other',
+ source_of_funds: 'debit',
+ identification: {
+ type: 'passport',
+ number: 'ABC123',
+ issuing_country: 'US',
+ date_of_expiry: '2027-07-07'
+ }
+ }
+ )
end
def test_transcript_scrubbing
@@ -636,6 +678,29 @@ def test_successful_credit
assert_equal 'Succeeded', response.message
end
+ def test_successful_money_transfer_payout_via_credit_individual_account_holder_type
+ @credit_card.first_name = 'John'
+ @credit_card.last_name = 'Doe'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge(account_holder_type: 'individual', payout: true))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_money_transfer_payout_via_credit_corporate_account_holder_type
+ @credit_card.name = 'ACME, Inc.'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge(account_holder_type: 'corporate'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_money_transfer_payout_reverts_to_credit_if_payout_sent_as_nil
+ @credit_card.first_name = 'John'
+ @credit_card.last_name = 'Doe'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge({ account_holder_type: 'individual', payout: nil }))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_store
response = @gateway_token.store(@credit_card, @options)
assert_success response
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 7256db54e3f..a5c69a6dae8 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -687,6 +687,113 @@ def test_successfully_passes_fund_type_and_fields
assert_success response
end
+ def test_successful_money_transfer_payout_via_credit
+ options = {
+ instruction_purpose: 'leisure',
+ account_holder_type: 'individual',
+ billing_address: address,
+ payout: true,
+ destination: {
+ account_holder: {
+ phone: {
+ number: '9108675309',
+ country_code: '1'
+ },
+ identification: {
+ type: 'passport',
+ number: '1234567890'
+ },
+ email: 'too_many_fields@checkout.com',
+ date_of_birth: '2004-10-27',
+ country_of_birth: 'US'
+ }
+ },
+ sender: {
+ type: 'individual',
+ first_name: 'Jane',
+ middle_name: 'Middle',
+ last_name: 'Doe',
+ reference: '012345',
+ reference_type: 'other',
+ source_of_funds: 'debit',
+ identification: {
+ type: 'passport',
+ number: '0987654321',
+ issuing_country: 'US',
+ date_of_expiry: '2027-07-07'
+ },
+ address: {
+ address1: '205 Main St',
+ address2: 'Apt G',
+ city: 'Winchestertonfieldville',
+ state: 'IA',
+ country: 'US',
+ zip: '12345'
+ },
+ date_of_birth: '2004-10-27',
+ country_of_birth: 'US',
+ nationality: 'US'
+ }
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.credit(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['instruction']['purpose'], 'leisure'
+ assert_equal request['destination']['account_holder']['phone']['number'], '9108675309'
+ assert_equal request['destination']['account_holder']['phone']['country_code'], '1'
+ assert_equal request['destination']['account_holder']['identification']['number'], '1234567890'
+ assert_equal request['destination']['account_holder']['identification']['type'], 'passport'
+ assert_equal request['destination']['account_holder']['email'], 'too_many_fields@checkout.com'
+ assert_equal request['destination']['account_holder']['date_of_birth'], '2004-10-27'
+ assert_equal request['destination']['account_holder']['country_of_birth'], 'US'
+ assert_equal request['sender']['type'], 'individual'
+ assert_equal request['sender']['first_name'], 'Jane'
+ assert_equal request['sender']['middle_name'], 'Middle'
+ assert_equal request['sender']['last_name'], 'Doe'
+ assert_equal request['sender']['reference'], '012345'
+ assert_equal request['sender']['reference_type'], 'other'
+ assert_equal request['sender']['source_of_funds'], 'debit'
+ assert_equal request['sender']['identification']['type'], 'passport'
+ assert_equal request['sender']['identification']['number'], '0987654321'
+ assert_equal request['sender']['identification']['issuing_country'], 'US'
+ assert_equal request['sender']['identification']['date_of_expiry'], '2027-07-07'
+ assert_equal request['sender']['address']['address_line1'], '205 Main St'
+ assert_equal request['sender']['address']['address_line2'], 'Apt G'
+ assert_equal request['sender']['address']['city'], 'Winchestertonfieldville'
+ assert_equal request['sender']['address']['state'], 'IA'
+ assert_equal request['sender']['address']['country'], 'US'
+ assert_equal request['sender']['address']['zip'], '12345'
+ assert_equal request['sender']['date_of_birth'], '2004-10-27'
+ assert_equal request['sender']['nationality'], 'US'
+ end.respond_with(successful_credit_response)
+ assert_success response
+ end
+
+ def test_transaction_successfully_reverts_to_regular_credit_when_payout_is_nil
+ options = {
+ instruction_purpose: 'leisure',
+ account_holder_type: 'individual',
+ billing_address: address,
+ payout: nil,
+ destination: {
+ account_holder: {
+ email: 'too_many_fields@checkout.com'
+ }
+ },
+ sender: {
+ type: 'individual'
+ }
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.credit(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ refute_includes data, 'email'
+ refute_includes data, 'sender'
+ end.respond_with(successful_credit_response)
+ assert_success response
+ end
+
def test_successful_refund
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
From 3931adf49fa188801abdaa0342e9479a910adf40 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 11 Aug 2023 09:30:37 -0500
Subject: [PATCH 176/390] Adding Oauth Response for access tokens
Add new OAuth Response error handling to PayTrace,
Quickbooks, Simetrik, Alelo, CheckoutV2 and Airwallex.
---
CHANGELOG | 1 +
.../billing/gateways/airwallex.rb | 12 ++++++---
lib/active_merchant/billing/gateways/alelo.rb | 26 ++++++++++++++++---
.../billing/gateways/checkout_v2.rb | 19 +++++++++++---
.../billing/gateways/pay_trace.rb | 21 ++++++++++-----
.../billing/gateways/quickbooks.rb | 19 ++++++++++----
.../billing/gateways/simetrik.rb | 18 ++++++++-----
lib/active_merchant/errors.rb | 12 +++++++++
test/remote/gateways/remote_airwallex_test.rb | 7 +++++
test/remote/gateways/remote_alelo_test.rb | 10 ++++---
.../gateways/remote_checkout_v2_test.rb | 16 ++++++++++++
test/remote/gateways/remote_pay_trace_test.rb | 17 ++++++++++++
.../remote/gateways/remote_quickbooks_test.rb | 17 ++++++++++++
test/remote/gateways/remote_simetrik_test.rb | 16 ++++++++++++
test/unit/gateways/airwallex_test.rb | 21 +++++++--------
test/unit/gateways/alelo_test.rb | 9 +++++++
test/unit/gateways/checkout_v2_test.rb | 22 ++++++++--------
test/unit/gateways/pay_trace_test.rb | 25 ++++++++++--------
test/unit/gateways/quickbooks_test.rb | 9 +++++++
test/unit/gateways/simetrik_test.rb | 22 ++++++++--------
20 files changed, 243 insertions(+), 76 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 637e1d66228..06c7cd03e7f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,7 @@
* Decidir: Add support for network tokens [almalee24] #4870
* Element: Fix credit card name bug [almalee24] #4898
* Adyen: Add payout endpoint [almalee24] #4885
+* Adding Oauth Response for access tokens [almalee24] #4851
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index fd1e06f427d..d9faced5ac2 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = setup_access_token
+ @access_token = options[:access_token] || setup_access_token
end
def purchase(money, card, options = {})
@@ -133,8 +133,14 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- response = ssl_post(build_request_url(:login), nil, token_headers)
- JSON.parse(response)['token']
+ raw_response = ssl_post(build_request_url(:login), nil, token_headers)
+ response = JSON.parse(raw_response)
+ if (token = response['token'])
+ token
+ else
+ oauth_response = Response.new(false, response['message'])
+ raise OAuthResponseError.new(oauth_response)
+ end
end
def build_request_url(action, id = nil)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index fd1cfc11a5f..381b5859372 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,8 +110,18 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
- Response.new(true, parsed[:access_token], parsed)
+ begin
+ raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ if (access_token = response[:access_token])
+ Response.new(true, access_token, response)
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def remote_encryption_key(access_token)
@@ -144,9 +154,11 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
+ raise e unless retry?(try_again, e, :access_token)
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -206,9 +218,11 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
+ if retry?(try_again, e, :encryption_key)
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -217,6 +231,10 @@ def commit(action, body, options, try_again = true)
end
end
+ def retry?(try_again, error, key)
+ try_again && %w(401 404).include?(error.response.code) && @options[key].present?
+ end
+
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index dc990d1fb52..2bd6e3da3f7 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -18,13 +18,13 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
- @access_token = nil
+ @access_token = options[:access_token] || nil
if options.has_key?(:secret_key)
requires!(options, :secret_key)
else
requires!(options, :client_id, :client_secret)
- @access_token = setup_access_token
+ @access_token ||= setup_access_token
end
super
@@ -428,8 +428,19 @@ def access_token_url
def setup_access_token
request = 'grant_type=client_credentials'
- response = parse(ssl_post(access_token_url, request, access_token_header))
- response['access_token']
+ begin
+ raw_response = ssl_post(access_token_url, request, access_token_header)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+
+ if (access_token = response['access_token'])
+ access_token
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def commit(action, post, options, authorization = nil, method = :post)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 53203d51f96..8c338687df1 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token
+ acquire_access_token unless options[:access_token]
end
def purchase(money, payment_or_customer_id, options = {})
@@ -187,10 +187,15 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = JSON.parse(response)
+ json_response = parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
+ if json_response.include?('error')
+ oauth_response = Response.new(false, json_response['error_description'])
+ raise OAuthResponseError.new(oauth_response)
+ else
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
+ end
end
private
@@ -373,6 +378,12 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
+ handle_final_response(action, response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -385,8 +396,6 @@ def commit(action, parameters)
test: test?,
error_code: success ? nil : error_code_from(response)
)
- rescue JSON::ParserError
- unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6d9f14f3445..6197581bbe7 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,12 +305,17 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- response = ssl_post(REFRESH_URI, data, headers)
- json_response = JSON.parse(response)
+ begin
+ response = ssl_post(REFRESH_URI, data, headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
+ end
end
def cvv_code_from(response)
@@ -358,6 +363,10 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
+
+ error_code = JSON.parse(response_error.response.body)['code']
+ raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
+
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index 5c436acab95..f3b0863eef8 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = {}
+ @access_token = options[:access_token] || {}
sign_access_token()
end
@@ -356,12 +356,18 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
- response = parse(ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- }))
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ begin
+ raw_response = ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ })
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ end
end
end
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index b017c45114a..4ec2f2efa23 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,6 +23,18 @@ def initialize(response, message = nil)
end
def to_s
+ if response.kind_of?(String)
+ if response.start_with?('Failed with')
+ return response
+ else
+ return "Failed with #{response}"
+ end
+ end
+
+ if response.respond_to?(:message)
+ return response.message if response.message.start_with?('Failed with')
+ end
+
"Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 5cbc4053c7d..24aa9fe3b61 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,6 +14,13 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
+ gateway.send :setup_access_token
+ end
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index be4d9ae9059..8a4fef24e7b 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::ResponseError) do
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,9 +145,11 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match %r{invalid_client}, response.message
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_match(/401/, error.message)
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 7361eecea9d..13149453ae4 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -160,6 +160,22 @@ def setup
)
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.send :setup_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 400 Bad Request'
+ end
+
def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index b10e5119e3a..6c56f840353 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -39,6 +39,23 @@ def test_acquire_token
assert_not_nil response['access_token']
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ gateway.send :acquire_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(1000, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ end
+
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index f3457706af5..6b295967b22 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,6 +18,23 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+ gateway.send :refresh_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index b1e2eb24daf..90f66e2f844 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,6 +78,22 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.send :fetch_access_token
+ end
+ end
+
+ def test_failed_authorize_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.authorize(@amount, @credit_card, @authorize_options_success)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index ade541ce88e..9d707bcba1c 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class AirwallexGateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -28,6 +18,15 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_setup_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
+ end
+
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 3e6c9f0c1c9..86e35e917f3 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,6 +19,15 @@ def setup
}
end
+ def test_fetch_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway .send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index a5c69a6dae8..1d3a15b0928 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CheckoutV2Gateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -17,7 +7,7 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
@gateway_api = CheckoutV2Gateway.new({
secret_key: '1111111111111',
public_key: '2222222222222'
@@ -27,6 +17,15 @@ def setup
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with 400 Bad Request/, error.message)
+ end
+
def test_successful_purchase
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
@@ -259,6 +258,7 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
def test_successful_render_for_oauth
processing_channel_id = 'abcd123'
+
response = stub_comms(@gateway_oauth, :ssl_request) do
@gateway_oauth.purchase(@amount, @credit_card, { processing_channel_id: processing_channel_id })
end.check_request do |_method, _endpoint, data, headers|
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 09f13807e83..42be462bbf0 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class PayTraceGateway < Gateway
- def acquire_access_token
- @options[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -24,6 +14,19 @@ def setup
}
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ access_token_response = {
+ error: 'invalid_grant',
+ error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ }.to_json
+ @gateway.expects(:ssl_post).returns(access_token_response)
+ @gateway.send(:acquire_access_token)
+ end
+
+ assert_match(/Failed with The provided authorization grant is invalid/, error.message)
+ end
+
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 7e48cce44ef..9b7a6f94a5b 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,6 +32,15 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
+ def test_refresh_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @oauth_2_gateway .send(:refresh_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb
index c120b2e99fe..f47a31203a9 100644
--- a/test/unit/gateways/simetrik_test.rb
+++ b/test/unit/gateways/simetrik_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SimetrikGateway < Gateway
- def fetch_access_token
- @access_token[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class SimetrikTest < Test::Unit::TestCase
def setup
@token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205'
@@ -17,7 +7,8 @@ def setup
@gateway = SimetrikGateway.new(
client_id: 'client_id',
client_secret: 'client_secret_key',
- audience: 'audience_url'
+ audience: 'audience_url',
+ access_token: { expires_at: Time.new.to_i }
)
@credit_card = CreditCard.new(
first_name: 'sergiod',
@@ -170,6 +161,15 @@ def test_success_purchase_with_billing_address
assert response.test?
end
+ def test_fetch_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway.send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_success_purchase_with_shipping_address
expected_body = JSON.parse(@authorize_capture_expected_body.dup)
expected_body['forward_payload']['order']['shipping_address'] = address
From 14f1c3c957aa8fbdaa6325776f464e4413527327 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 29 Sep 2023 16:33:32 -0500
Subject: [PATCH 177/390] CheckoutV2: Update stored credentials
Only set merchant_initiated to false if initiator in stored_credential
is cardholder.
Unit
59 tests, 335 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote
93 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/checkout_v2.rb | 2 +-
test/unit/gateways/checkout_v2_test.rb | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 06c7cd03e7f..6bd8b9ae583 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -21,6 +21,7 @@
* Element: Fix credit card name bug [almalee24] #4898
* Adyen: Add payout endpoint [almalee24] #4885
* Adding Oauth Response for access tokens [almalee24] #4851
+* CheckoutV2: Update stored credentials [almalee24] #4901
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index 2bd6e3da3f7..bed352e9a3b 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -288,7 +288,7 @@ def merchant_initiated_override(post, options)
end
def add_stored_credentials_using_normalized_fields(post, options)
- if options[:stored_credential][:initial_transaction] == true
+ if options[:stored_credential][:initiator] == 'cardholder'
post[:merchant_initiated] = false
else
post[:source][:stored] = true
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 1d3a15b0928..68eced66caf 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -362,6 +362,7 @@ def test_successful_purchase_with_stored_credentials
initial_response = stub_comms(@gateway, :ssl_request) do
initial_options = {
stored_credential: {
+ initiator: 'cardholder',
initial_transaction: true,
reason_type: 'installment'
}
From b10e6c2fcc54b1c5af6714ee70548ffec0e700b2 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 3 Oct 2023 10:07:49 -0500
Subject: [PATCH 178/390] Revert "Adding Oauth Response for access tokens"
This reverts commit 3931adf49fa188801abdaa0342e9479a910adf40.
---
CHANGELOG | 1 +
.../billing/gateways/airwallex.rb | 12 +++------
lib/active_merchant/billing/gateways/alelo.rb | 26 +++----------------
.../billing/gateways/checkout_v2.rb | 19 +++-----------
.../billing/gateways/pay_trace.rb | 21 +++++----------
.../billing/gateways/quickbooks.rb | 19 ++++----------
.../billing/gateways/simetrik.rb | 18 +++++--------
lib/active_merchant/errors.rb | 12 ---------
test/remote/gateways/remote_airwallex_test.rb | 7 -----
test/remote/gateways/remote_alelo_test.rb | 10 +++----
.../gateways/remote_checkout_v2_test.rb | 16 ------------
test/remote/gateways/remote_pay_trace_test.rb | 17 ------------
.../remote/gateways/remote_quickbooks_test.rb | 17 ------------
test/remote/gateways/remote_simetrik_test.rb | 16 ------------
test/unit/gateways/airwallex_test.rb | 21 ++++++++-------
test/unit/gateways/alelo_test.rb | 9 -------
test/unit/gateways/checkout_v2_test.rb | 22 ++++++++--------
test/unit/gateways/pay_trace_test.rb | 25 ++++++++----------
test/unit/gateways/quickbooks_test.rb | 9 -------
test/unit/gateways/simetrik_test.rb | 22 ++++++++--------
20 files changed, 77 insertions(+), 242 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 6bd8b9ae583..bb5f05ac172 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -22,6 +22,7 @@
* Adyen: Add payout endpoint [almalee24] #4885
* Adding Oauth Response for access tokens [almalee24] #4851
* CheckoutV2: Update stored credentials [almalee24] #4901
+* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index d9faced5ac2..fd1e06f427d 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = options[:access_token] || setup_access_token
+ @access_token = setup_access_token
end
def purchase(money, card, options = {})
@@ -133,14 +133,8 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- raw_response = ssl_post(build_request_url(:login), nil, token_headers)
- response = JSON.parse(raw_response)
- if (token = response['token'])
- token
- else
- oauth_response = Response.new(false, response['message'])
- raise OAuthResponseError.new(oauth_response)
- end
+ response = ssl_post(build_request_url(:login), nil, token_headers)
+ JSON.parse(response)['token']
end
def build_request_url(action, id = nil)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index 381b5859372..fd1cfc11a5f 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,18 +110,8 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- begin
- raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
- if (access_token = response[:access_token])
- Response.new(true, access_token, response)
- else
- raise OAuthResponseError.new(response)
- end
- end
+ parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
+ Response.new(true, parsed[:access_token], parsed)
end
def remote_encryption_key(access_token)
@@ -154,11 +144,9 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
- rescue ActiveMerchant::OAuthResponseError => e
- raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise e unless retry?(try_again, e, :access_token)
+ raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -218,11 +206,9 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
- rescue ActiveMerchant::OAuthResponseError => e
- raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if retry?(try_again, e, :encryption_key)
+ if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -231,10 +217,6 @@ def commit(action, body, options, try_again = true)
end
end
- def retry?(try_again, error, key)
- try_again && %w(401 404).include?(error.response.code) && @options[key].present?
- end
-
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index bed352e9a3b..d0dddaff429 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -18,13 +18,13 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
- @access_token = options[:access_token] || nil
+ @access_token = nil
if options.has_key?(:secret_key)
requires!(options, :secret_key)
else
requires!(options, :client_id, :client_secret)
- @access_token ||= setup_access_token
+ @access_token = setup_access_token
end
super
@@ -428,19 +428,8 @@ def access_token_url
def setup_access_token
request = 'grant_type=client_credentials'
- begin
- raw_response = ssl_post(access_token_url, request, access_token_header)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
-
- if (access_token = response['access_token'])
- access_token
- else
- raise OAuthResponseError.new(response)
- end
- end
+ response = parse(ssl_post(access_token_url, request, access_token_header))
+ response['access_token']
end
def commit(action, post, options, authorization = nil, method = :post)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 8c338687df1..53203d51f96 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token unless options[:access_token]
+ acquire_access_token
end
def purchase(money, payment_or_customer_id, options = {})
@@ -187,15 +187,10 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = parse(response)
+ json_response = JSON.parse(response)
- if json_response.include?('error')
- oauth_response = Response.new(false, json_response['error_description'])
- raise OAuthResponseError.new(oauth_response)
- else
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
- end
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
end
private
@@ -378,12 +373,6 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
- handle_final_response(action, response)
- rescue JSON::ParserError
- unparsable_response(raw_response)
- end
-
- def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -396,6 +385,8 @@ def handle_final_response(action, response)
test: test?,
error_code: success ? nil : error_code_from(response)
)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6197581bbe7..6d9f14f3445 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,17 +305,12 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- begin
- response = ssl_post(REFRESH_URI, data, headers)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- json_response = JSON.parse(response)
+ response = ssl_post(REFRESH_URI, data, headers)
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
- end
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
end
def cvv_code_from(response)
@@ -363,10 +358,6 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
-
- error_code = JSON.parse(response_error.response.body)['code']
- raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
-
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index f3b0863eef8..5c436acab95 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = options[:access_token] || {}
+ @access_token = {}
sign_access_token()
end
@@ -356,18 +356,12 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
+ response = parse(ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ }))
- begin
- raw_response = ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- })
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- response = parse(raw_response)
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
- end
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
end
end
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index 4ec2f2efa23..b017c45114a 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,18 +23,6 @@ def initialize(response, message = nil)
end
def to_s
- if response.kind_of?(String)
- if response.start_with?('Failed with')
- return response
- else
- return "Failed with #{response}"
- end
- end
-
- if response.respond_to?(:message)
- return response.message if response.message.start_with?('Failed with')
- end
-
"Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 24aa9fe3b61..5cbc4053c7d 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,13 +14,6 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
- gateway.send :setup_access_token
- end
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index 8a4fef24e7b..be4d9ae9059 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ error = assert_raises(ActiveMerchant::ResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,11 +145,9 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_match(/401/, error.message)
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid_client}, response.message
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 13149453ae4..7361eecea9d 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -160,22 +160,6 @@ def setup
)
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
- gateway.send :setup_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with 400 Bad Request'
- end
-
def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index 6c56f840353..b10e5119e3a 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -39,23 +39,6 @@ def test_acquire_token
assert_not_nil response['access_token']
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
- gateway.send :acquire_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
-
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(1000, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
- end
-
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index 6b295967b22..f3457706af5 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,23 +18,6 @@ def setup
}
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
- gateway.send :refresh_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
-
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with 401 Unauthorized'
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index 90f66e2f844..b1e2eb24daf 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,22 +78,6 @@ def setup
}
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
- gateway.send :fetch_access_token
- end
- end
-
- def test_failed_authorize_with_failed_access_token
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
- gateway.authorize(@amount, @credit_card, @authorize_options_success)
- end
-
- assert_equal error.message, 'Failed with 401 Unauthorized'
- end
-
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index 9d707bcba1c..ade541ce88e 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,10 +1,20 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class AirwallexGateway
+ def setup_access_token
+ '12345678'
+ end
+ end
+ end
+end
+
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -18,15 +28,6 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
- def test_setup_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
- @gateway.send(:setup_access_token)
- end
-
- assert_match(/Failed with Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
- end
-
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 86e35e917f3..3e6c9f0c1c9 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,15 +19,6 @@ def setup
}
end
- def test_fetch_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @gateway .send(:fetch_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 68eced66caf..309b32587e0 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,5 +1,15 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CheckoutV2Gateway
+ def setup_access_token
+ '12345678'
+ end
+ end
+ end
+end
+
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -7,7 +17,7 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
@gateway_api = CheckoutV2Gateway.new({
secret_key: '1111111111111',
public_key: '2222222222222'
@@ -17,15 +27,6 @@ def setup
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
- def test_setup_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
- @gateway.send(:setup_access_token)
- end
-
- assert_match(/Failed with 400 Bad Request/, error.message)
- end
-
def test_successful_purchase
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
@@ -258,7 +259,6 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
def test_successful_render_for_oauth
processing_channel_id = 'abcd123'
-
response = stub_comms(@gateway_oauth, :ssl_request) do
@gateway_oauth.purchase(@amount, @credit_card, { processing_channel_id: processing_channel_id })
end.check_request do |_method, _endpoint, data, headers|
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 42be462bbf0..09f13807e83 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,10 +1,20 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayTraceGateway < Gateway
+ def acquire_access_token
+ @options[:access_token] = SecureRandom.hex(16)
+ end
+ end
+ end
+end
+
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -14,19 +24,6 @@ def setup
}
end
- def test_setup_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- access_token_response = {
- error: 'invalid_grant',
- error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
- }.to_json
- @gateway.expects(:ssl_post).returns(access_token_response)
- @gateway.send(:acquire_access_token)
- end
-
- assert_match(/Failed with The provided authorization grant is invalid/, error.message)
- end
-
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 9b7a6f94a5b..7e48cce44ef 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,15 +32,6 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
- def test_refresh_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @oauth_2_gateway .send(:refresh_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb
index f47a31203a9..c120b2e99fe 100644
--- a/test/unit/gateways/simetrik_test.rb
+++ b/test/unit/gateways/simetrik_test.rb
@@ -1,5 +1,15 @@
require 'test_helper'
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SimetrikGateway < Gateway
+ def fetch_access_token
+ @access_token[:access_token] = SecureRandom.hex(16)
+ end
+ end
+ end
+end
+
class SimetrikTest < Test::Unit::TestCase
def setup
@token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205'
@@ -7,8 +17,7 @@ def setup
@gateway = SimetrikGateway.new(
client_id: 'client_id',
client_secret: 'client_secret_key',
- audience: 'audience_url',
- access_token: { expires_at: Time.new.to_i }
+ audience: 'audience_url'
)
@credit_card = CreditCard.new(
first_name: 'sergiod',
@@ -161,15 +170,6 @@ def test_success_purchase_with_billing_address
assert response.test?
end
- def test_fetch_access_token_should_rise_an_exception_under_bad_request
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @gateway.send(:fetch_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_success_purchase_with_shipping_address
expected_body = JSON.parse(@authorize_capture_expected_body.dup)
expected_body['forward_payload']['order']['shipping_address'] = address
From f2078558c1ebe2629e6b0a968b39df84084216ba Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Tue, 3 Oct 2023 14:02:59 -0500
Subject: [PATCH 179/390] Braintree: Create credit card nonce (#4897)
Co-authored-by: Gustavo Sanmartin
---
CHANGELOG | 1 +
.../billing/gateways/braintree/token_nonce.rb | 75 +++++--
.../remote_braintree_token_nonce_test.rb | 13 +-
.../gateways/braintree_token_nonce_test.rb | 187 ++++++++++++++++++
4 files changed, 259 insertions(+), 17 deletions(-)
create mode 100644 test/unit/gateways/braintree_token_nonce_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index bb5f05ac172..abf67110baf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -23,6 +23,7 @@
* Adding Oauth Response for access tokens [almalee24] #4851
* CheckoutV2: Update stored credentials [almalee24] #4901
* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
+* Braintree: Create credit card nonce [gasb150] #4897
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
index 37417dd732a..eeaee734fc2 100644
--- a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
+++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
@@ -29,7 +29,7 @@ def create_token_nonce_for_payment_method(payment_method)
json_response = JSON.parse(resp)
message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present?
- token = json_response.dig('data', 'tokenizeUsBankAccount', 'paymentMethod', 'id')
+ token = token_from(payment_method, json_response)
return token, message
end
@@ -41,7 +41,7 @@ def client_token
private
- def graphql_query
+ def graphql_bank_query
<<-GRAPHQL
mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
tokenizeUsBankAccount(input: $input) {
@@ -58,6 +58,23 @@ def graphql_query
GRAPHQL
end
+ def graphql_credit_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
def billing_address_from_options
return nil if options[:billing_address].blank?
@@ -72,7 +89,42 @@ def billing_address_from_options
}.compact
end
+ def build_nonce_credit_card_request(payment_method)
+ billing_address = billing_address_from_options
+ key_replacements = { city: :locality, state: :region, zipCode: :postalCode }
+ billing_address.transform_keys! { |key| key_replacements[key] || key }
+ {
+ creditCard: {
+ number: payment_method.number,
+ expirationYear: payment_method.year.to_s,
+ expirationMonth: payment_method.month.to_s.rjust(2, '0'),
+ cvv: payment_method.verification_value,
+ cardholderName: payment_method.name,
+ billingAddress: billing_address
+ }
+ }
+ end
+
def build_nonce_request(payment_method)
+ input = payment_method.is_a?(Check) ? build_nonce_bank_request(payment_method) : build_nonce_credit_card_request(payment_method)
+ graphql_query = payment_method.is_a?(Check) ? graphql_bank_query : graphql_credit_query
+
+ {
+ clientSdkMetadata: {
+ platform: 'web',
+ source: 'client',
+ integration: 'custom',
+ sessionId: SecureRandom.uuid,
+ version: '3.83.0'
+ },
+ query: graphql_query,
+ variables: {
+ input: input
+ }
+ }.to_json
+ end
+
+ def build_nonce_bank_request(payment_method)
input = {
usBankAccount: {
achMandate: options[:ach_mandate],
@@ -94,19 +146,12 @@ def build_nonce_request(payment_method)
}
end
- {
- clientSdkMetadata: {
- platform: 'web',
- source: 'client',
- integration: 'custom',
- sessionId: SecureRandom.uuid,
- version: '3.83.0'
- },
- query: graphql_query,
- variables: {
- input: input
- }
- }.to_json
+ input
+ end
+
+ def token_from(payment_method, response)
+ tokenized_field = payment_method.is_a?(Check) ? 'tokenizeUsBankAccount' : 'tokenizeCreditCard'
+ response.dig('data', tokenized_field, 'paymentMethod', 'id')
end
end
end
diff --git a/test/remote/gateways/remote_braintree_token_nonce_test.rb b/test/remote/gateways/remote_braintree_token_nonce_test.rb
index 312af9361b6..cbc8dbc3c24 100644
--- a/test/remote/gateways/remote_braintree_token_nonce_test.rb
+++ b/test/remote/gateways/remote_braintree_token_nonce_test.rb
@@ -47,7 +47,7 @@ def test_unsucesfull_create_token_with_invalid_state
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'state' of variable 'input' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'state' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
end
def test_unsucesfull_create_token_with_invalid_zip_code
@@ -57,7 +57,7 @@ def test_unsucesfull_create_token_with_invalid_zip_code
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'zipCode' of variable 'input' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'zipCode' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
end
def test_url_generation
@@ -80,4 +80,13 @@ def test_url_generation
assert_equal 'https://payments.braintree-api.com/graphql', generator.url
end
+
+ def test_successfully_create_token_nonce_for_credit_card
+ generator = TokenNonce.new(@braintree_backend, @options)
+ credit_card = credit_card('4111111111111111')
+ tokenized_credit_card, err_messages = generator.create_token_nonce_for_payment_method(credit_card)
+ assert_not_nil tokenized_credit_card
+ assert_match %r(^tokencc_), tokenized_credit_card
+ assert_nil err_messages
+ end
end
diff --git a/test/unit/gateways/braintree_token_nonce_test.rb b/test/unit/gateways/braintree_token_nonce_test.rb
new file mode 100644
index 00000000000..d75ff5c405e
--- /dev/null
+++ b/test/unit/gateways/braintree_token_nonce_test.rb
@@ -0,0 +1,187 @@
+require 'test_helper'
+
+class BraintreeTokenNonceTest < Test::Unit::TestCase
+ def setup
+ @gateway = BraintreeBlueGateway.new(
+ merchant_id: 'test',
+ public_key: 'test',
+ private_key: 'test',
+ test: true
+ )
+
+ @braintree_backend = @gateway.instance_eval { @braintree_gateway }
+
+ @options = {
+ billing_address: {
+ name: 'Adrain',
+ address1: '96706 Onie Plains',
+ address2: '01897 Alysa Lock',
+ country: 'XXX',
+ city: 'Miami',
+ state: 'FL',
+ zip: '32191',
+ phone_number: '693-630-6935'
+ },
+ ach_mandate: 'ach_mandate'
+ }
+ @generator = TokenNonce.new(@braintree_backend, @options)
+ end
+
+ def test_build_nonce_request_for_credit_card
+ credit_card = credit_card('4111111111111111')
+ response = @generator.send(:build_nonce_request, credit_card)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
+ assert_includes parse_response['variables']['input'], 'creditCard'
+
+ credit_card_input = parse_response['variables']['input']['creditCard']
+
+ assert_equal credit_card_input['number'], credit_card.number
+ assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
+ assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
+ assert_equal credit_card_input['cvv'], credit_card.verification_value
+ assert_equal credit_card_input['cardholderName'], credit_card.name
+ assert_billing_address_mapping(credit_card_input, credit_card)
+ end
+
+ def test_build_nonce_request_for_bank_account
+ bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ response = @generator.send(:build_nonce_request, bank_account)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(bank_account_query)
+ assert_includes parse_response['variables']['input'], 'usBankAccount'
+
+ bank_account_input = parse_response['variables']['input']['usBankAccount']
+
+ assert_equal bank_account_input['routingNumber'], bank_account.routing_number
+ assert_equal bank_account_input['accountNumber'], bank_account.account_number
+ assert_equal bank_account_input['accountType'], bank_account.account_type.upcase
+ assert_equal bank_account_input['achMandate'], @options[:ach_mandate]
+
+ assert_billing_address_mapping(bank_account_input, bank_account)
+
+ assert_equal bank_account_input['individualOwner']['firstName'], bank_account.first_name
+ assert_equal bank_account_input['individualOwner']['lastName'], bank_account.last_name
+ end
+
+ def test_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_credit_response)
+ assert_match(/tokencc_/, c_token)
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_bank_response)
+ assert_match(/tokenusbankacct_/, b_token)
+ end
+
+ def test_nil_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_bank_response)
+ assert_nil c_token
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_credit_response)
+ assert_nil b_token
+ end
+
+ def assert_billing_address_mapping(request_input, payment_method)
+ assert_equal request_input['billingAddress']['streetAddress'], @options[:billing_address][:address1]
+ assert_equal request_input['billingAddress']['extendedAddress'], @options[:billing_address][:address2]
+
+ if payment_method.is_a?(Check)
+ assert_equal request_input['billingAddress']['city'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['state'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['zipCode'], @options[:billing_address][:zip]
+ else
+ assert_equal request_input['billingAddress']['locality'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['region'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['postalCode'], @options[:billing_address][:zip]
+ end
+ end
+
+ def assert_client_sdk_metadata(parse_response)
+ assert_equal parse_response['clientSdkMetadata']['platform'], 'web'
+ assert_equal parse_response['clientSdkMetadata']['source'], 'client'
+ assert_equal parse_response['clientSdkMetadata']['integration'], 'custom'
+ assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i, parse_response['clientSdkMetadata']['sessionId'])
+ assert_equal parse_response['clientSdkMetadata']['version'], '3.83.0'
+ end
+
+ private
+
+ def normalize_graph(graph)
+ graph.gsub(/\s+/, ' ').strip
+ end
+
+ def bank_account_query
+ <<-GRAPHQL
+ mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
+ tokenizeUsBankAccount(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on UsBankAccountDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def credit_card_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def token_credit_response
+ {
+ 'data' => {
+ 'tokenizeCreditCard' => {
+ 'paymentMethod' => {
+ 'id' => 'tokencc_bc_72n3ms_74wsn3_jp2vn4_gjj62v_g33',
+ 'details' => {
+ 'last4' => '1111'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => 'a093afbb-42a9-4a85-973f-0ca79dff9ba6'
+ }
+ }
+ end
+
+ def token_bank_response
+ {
+ 'data' => {
+ 'tokenizeUsBankAccount' => {
+ 'paymentMethod' => {
+ 'id' => 'tokenusbankacct_bc_zrg45z_7wz95v_nscrks_q4zpjs_5m7',
+ 'details' => {
+ 'last4' => '0125'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => '769b26d5-27e4-4602-b51d-face8b6ffdd5'
+ }
+ }
+ end
+end
From a585f304ae751ec9371af12021f690ce3a6ecc01 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 3 Oct 2023 08:41:41 -0500
Subject: [PATCH 180/390] Adyen: Fix bug for shopperEmail
Move add_shopper_data back to add_extra_data to ensure that shopperEmail
is being sent in all transaction types.
Unit:
112 tests, 589 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
137 tests, 455 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
91.9708% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 13 ++++++++-----
test/unit/gateways/adyen_test.rb | 1 +
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index abf67110baf..74af5e77358 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -24,6 +24,7 @@
* CheckoutV2: Update stored credentials [almalee24] #4901
* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
* Braintree: Create credit card nonce [gasb150] #4897
+* Adyen: Fix shopperEmail bug [almalee24] #4904
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index e80ffb472e7..1b5ae57475e 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -105,6 +105,7 @@ def credit(money, payment, options = {})
add_fraud_offset(post, options)
add_fund_source(post, options)
add_recurring_contract(post, options)
+ add_shopper_data(post, payment, options)
if (address = options[:billing_address] || options[:address]) && address[:country]
add_billing_address(post, address)
@@ -255,6 +256,7 @@ def add_extra_data(post, payment, options)
post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
post[:store] = options[:store] if options[:store]
+ add_shopper_data(post, payment, options)
add_additional_data(post, payment, options)
add_risk_data(post, options)
add_shopper_reference(post, options)
@@ -553,7 +555,6 @@ def add_payment(post, payment, options, action = nil)
end
def add_bank_account(post, bank_account, options, action)
- add_shopper_data(post, bank_account, options)
bank = {
bankAccountNumber: bank_account.account_number,
ownerName: bank_account.name,
@@ -567,7 +568,6 @@ def add_bank_account(post, bank_account, options, action)
end
def add_card(post, credit_card)
- add_shopper_data(post, credit_card, options)
card = {
expiryMonth: credit_card.month,
expiryYear: credit_card.year,
@@ -583,9 +583,12 @@ def add_card(post, credit_card)
end
def add_shopper_data(post, payment, options)
- post[:shopperName] = {}
- post[:shopperName][:firstName] = payment.first_name
- post[:shopperName][:lastName] = payment.last_name
+ if payment && !payment.is_a?(String)
+ post[:shopperName] = {}
+ post[:shopperName][:firstName] = payment.first_name
+ post[:shopperName][:lastName] = payment.last_name
+ end
+
post[:shopperEmail] = options[:email] if options[:email]
post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
end
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index cab4ab63910..ac10a3b7f81 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -184,6 +184,7 @@ def test_successful_authorize_with_recurring_contract_type
stub_comms do
@gateway.authorize(100, @credit_card, @options.merge({ recurring_contract_type: 'ONECLICK' }))
end.check_request do |_endpoint, data, _headers|
+ assert_equal 'john.smith@test.com', JSON.parse(data)['shopperEmail']
assert_equal 'ONECLICK', JSON.parse(data)['recurring']['contract']
end.respond_with(successful_authorize_response)
end
From b5d68f310303453f3ee37ab66665032319a572e2 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 4 Oct 2023 09:38:16 -0700
Subject: [PATCH 181/390] Add new cabal bin range
---
CHANGELOG | 1 +
lib/active_merchant/billing/credit_card_methods.rb | 3 ++-
test/unit/credit_card_methods_test.rb | 1 +
3 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 74af5e77358..5aea7606ebf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -25,6 +25,7 @@
* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
* Braintree: Create credit card nonce [gasb150] #4897
* Adyen: Fix shopperEmail bug [almalee24] #4904
+* Add Cabal card bin ranges [yunnydang] #4908
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 29992b6d617..bd9fe0c9197 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -222,7 +222,8 @@ module CreditCardMethods
58965700..58965799,
60352200..60352299,
65027200..65027299,
- 65008700..65008700
+ 65008700..65008700,
+ 65090000..65090099
]
MADA_RANGES = [
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index 9ff10d72eb0..e4c1240131c 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -363,6 +363,7 @@ def test_should_detect_cabal_card
assert_equal 'cabal', CreditCard.brand?('6035224400000000')
assert_equal 'cabal', CreditCard.brand?('6502723300000000')
assert_equal 'cabal', CreditCard.brand?('6500870000000000')
+ assert_equal 'cabal', CreditCard.brand?('6509000000000000')
end
def test_should_detect_unionpay_card
From 3c578b92caca64840c715772c186d231b456cb18 Mon Sep 17 00:00:00 2001
From: cristian
Date: Thu, 5 Oct 2023 09:21:11 -0500
Subject: [PATCH 182/390] Kushki: Fixing issue with 3DS info on visa cc (#4899)
Summary:
------------------------------
Fixes a bug for Kushki that fails transactions when
3DS info is passed for VISA Credit Cards.
SER-830
Remote Test:
------------------------------
Finished in 87.534566 seconds.
24 tests, 71 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Unit Tests:
------------------------------
Finished in 37.30761 seconds.
5624 tests, 78070 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
772 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/kushki.rb | 2 +-
test/remote/gateways/remote_kushki_test.rb | 15 +++++++++++++++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5aea7606ebf..36261672e07 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -26,6 +26,7 @@
* Braintree: Create credit card nonce [gasb150] #4897
* Adyen: Fix shopperEmail bug [almalee24] #4904
* Add Cabal card bin ranges [yunnydang] #4908
+* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index 0bf56881cda..fbdae802e96 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -230,7 +230,7 @@ def add_three_d_secure(post, payment_method, options)
elsif payment_method.brand == 'visa'
post[:threeDomainSecure][:acceptRisk] = three_d_secure[:eci] == '07'
post[:threeDomainSecure][:cavv] = three_d_secure[:cavv]
- post[:threeDomainSecure][:xid] = three_d_secure[:xid]
+ post[:threeDomainSecure][:xid] = three_d_secure[:xid] if three_d_secure[:xid].present?
else
raise ArgumentError.new 'Kushki supports 3ds2 authentication for only Visa and Mastercard brands.'
end
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index e310d6bb91e..14ab10b18c9 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -195,6 +195,21 @@ def test_successful_3ds2_authorize_with_visa_card
assert_match %r(^\d+$), response.authorization
end
+ def test_successful_3ds2_authorize_with_visa_card_with_optional_xid
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ eci: '07'
+ }
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
def test_successful_3ds2_authorize_with_master_card
options = {
currency: 'PEN',
From 6494fb6e9393a86824e1f6b938e604e919d69b2f Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Wed, 4 Oct 2023 18:12:13 -0400
Subject: [PATCH 183/390] Adyen: Add `address_override` to swap address1 and
address2
CER-777
Adyen address fields are more European centric: `houseNumberOrName` and `street`.
`address1` is currently mapped to `street` and `address2` is currently mapped to `houseNumberOrName` which causes AVS errors for users. This may be worth making a permanent change and removing the override in the future.
Remote Tests:
138 tests, 457 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
92.029% passed
*11 failures are also failing on master
Unit Tests:
113 tests, 595 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Local Tests:
5634 tests, 78165 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
lib/active_merchant/billing/gateways/adyen.rb | 16 ++++++++--------
test/remote/gateways/remote_adyen_test.rb | 14 ++++++++++++++
test/unit/gateways/adyen_test.rb | 10 ++++++++++
3 files changed, 32 insertions(+), 8 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 1b5ae57475e..43cd1e9029e 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -108,7 +108,7 @@ def credit(money, payment, options = {})
add_shopper_data(post, payment, options)
if (address = options[:billing_address] || options[:address]) && address[:country]
- add_billing_address(post, address)
+ add_billing_address(post, options, address)
end
post[:dateOfBirth] = options[:date_of_birth] if options[:date_of_birth]
@@ -493,8 +493,8 @@ def add_recurring_processing_model(post, options)
def add_address(post, options)
if address = options[:shipping_address]
post[:deliveryAddress] = {}
- post[:deliveryAddress][:street] = address[:address1] || 'NA'
- post[:deliveryAddress][:houseNumberOrName] = address[:address2] || 'NA'
+ post[:deliveryAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
+ post[:deliveryAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
post[:deliveryAddress][:postalCode] = address[:zip] if address[:zip]
post[:deliveryAddress][:city] = address[:city] || 'NA'
post[:deliveryAddress][:stateOrProvince] = get_state(address)
@@ -503,14 +503,14 @@ def add_address(post, options)
return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash)
if (address = options[:billing_address] || options[:address]) && address[:country]
- add_billing_address(post, address)
+ add_billing_address(post, options, address)
end
end
- def add_billing_address(post, address)
+ def add_billing_address(post, options, address)
post[:billingAddress] = {}
- post[:billingAddress][:street] = address[:address1] || 'NA'
- post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
+ post[:billingAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
+ post[:billingAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
post[:billingAddress][:city] = address[:city] || 'NA'
post[:billingAddress][:stateOrProvince] = get_state(address)
@@ -738,7 +738,7 @@ def add_fund_source(post, options)
end
if (address = fund_source[:billing_address])
- add_billing_address(post[:fundSource], address)
+ add_billing_address(post[:fundSource], options, address)
end
end
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index 8f772710cd7..dfa28c6ded1 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -1675,6 +1675,20 @@ def test_successful_authorize_with_alternate_kosovo_code
assert_success response
end
+ def test_successful_authorize_with_address_override
+ address = {
+ address1: 'Bag End',
+ address2: '123 Hobbiton Way',
+ city: 'Hobbiton',
+ state: 'Derbyshire',
+ country: 'GB',
+ zip: 'DE45 1PP'
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address, address_override: true))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
private
def stored_credential_options(*args, ntid: nil)
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index ac10a3b7f81..e0355cfba68 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -1061,6 +1061,16 @@ def test_add_address
assert_equal @options[:shipping_address][:country], post[:deliveryAddress][:country]
end
+ def test_address_override_that_will_swap_housenumberorname_and_street
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(address_override: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"houseNumberOrName":"456 My Street"/, data)
+ assert_match(/"street":"Apt 1"/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_successful_auth_phone
options = @options.merge(billing_address: { phone: 1234567890 })
response = stub_comms do
From 2165af890b15d2993674b889970d3aec597408a2 Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 2 Oct 2023 11:51:13 -0400
Subject: [PATCH 184/390] Adyen: Update MIT flagging for NT
ECS-3158
The existing logic for MIT flagging for any NT card (ApplePay, GooglePay, or NetworkToken)
assume that they cannot be used with a `shopperInteraction` of `ContAuth`. This is not true as
a merchant can perform MIT transactions with these payment methods if they skip adding MPI data.
This commit updates the `shopper_interaction` logic so that NT payment methods on subsequent transactions
will be flagged as `ContAuth` and skip adding MPI data for them.
This commit also comments out Adyen's remote store tests as they are failing.
A new ticket, ECS-3205, has been created to fix them.
Remote Tests:
128 tests, 449 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 26 ++-
test/remote/gateways/remote_adyen_test.rb | 217 +++++++++++-------
test/unit/gateways/adyen_test.rb | 23 ++
4 files changed, 169 insertions(+), 98 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 36261672e07..eeb1924c09e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -27,6 +27,7 @@
* Adyen: Fix shopperEmail bug [almalee24] #4904
* Add Cabal card bin ranges [yunnydang] #4908
* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
+* Adyen: Add MIT flagging for Network Tokens [aenand] #4905
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 43cd1e9029e..c6af2ae7198 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -55,9 +55,9 @@ def authorize(money, payment, options = {})
requires!(options, :order_id)
post = init_post(options)
add_invoice(post, money, options)
+ add_stored_credentials(post, payment, options)
add_payment(post, payment, options)
add_extra_data(post, payment, options)
- add_stored_credentials(post, payment, options)
add_address(post, options)
add_installments(post, options) if options[:installments]
add_3ds(post, options)
@@ -138,9 +138,9 @@ def store(credit_card, options = {})
requires!(options, :order_id)
post = init_post(options)
add_invoice(post, 0, options)
+ add_stored_credentials(post, credit_card, options)
add_payment(post, credit_card, options)
add_extra_data(post, credit_card, options)
- add_stored_credentials(post, credit_card, options)
add_address(post, options)
add_network_transaction_reference(post, options)
options[:recurring_contract_type] ||= 'RECURRING'
@@ -463,17 +463,23 @@ def add_shopper_reference(post, options)
end
def add_shopper_interaction(post, payment, options = {})
- if (options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder') ||
- (payment.respond_to?(:verification_value) && payment.verification_value && options.dig(:stored_credential, :initial_transaction).nil?) ||
- payment.is_a?(NetworkTokenizationCreditCard)
- shopper_interaction = 'Ecommerce'
- else
- shopper_interaction = 'ContAuth'
- end
+ shopper_interaction = ecommerce_interaction?(payment, options) ? 'Ecommerce' : 'ContAuth'
post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
end
+ def ecommerce_interaction?(payment, options)
+ if options.dig(:stored_credential, :initial_transaction).nil? && payment.is_a?(NetworkTokenizationCreditCard)
+ true
+ elsif options.dig(:stored_credential, :initial_transaction).nil? && (payment.respond_to?(:verification_value) && payment.verification_value)
+ true
+ elsif options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder'
+ true
+ else
+ false
+ end
+ end
+
def add_recurring_processing_model(post, options)
return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model]
@@ -612,7 +618,7 @@ def add_reference(post, authorization, options = {})
end
def add_mpi_data_for_network_tokenization_card(post, payment, options)
- return if options[:skip_mpi_data] == 'Y'
+ return if options[:skip_mpi_data] == 'Y' || post[:shopperInteraction] == 'ContAuth'
post[:mpiData] = {}
post[:mpiData][:authenticationResponse] = 'Y'
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index dfa28c6ded1..ab2572f7b94 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -951,99 +951,107 @@ def test_failed_synchronous_adjust_using_adjust_data
assert_equal 'Refused', adjust.message
end
- def test_successful_store
- assert response = @gateway.store(@credit_card, @options)
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_store
+ # assert response = @gateway.store(@credit_card, @options)
- assert_success response
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
- end
+ # assert_success response
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
+ # end
- def test_successful_store_with_bank_account
- assert response = @gateway.store(@bank_account, @options)
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_store_with_bank_account
+ # assert response = @gateway.store(@bank_account, @options)
- assert_success response
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
- end
+ # assert_success response
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
+ # end
- def test_successful_unstore
- assert response = @gateway.store(@credit_card, @options)
+ # Failing due to store not working
+ # def test_successful_unstore
+ # assert response = @gateway.store(@credit_card, @options)
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
- shopper_reference = response.params['additionalData']['recurring.shopperReference']
- recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ # shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- assert response = @gateway.unstore(shopper_reference: shopper_reference,
- recurring_detail_reference: recurring_detail_reference)
+ # assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ # recurring_detail_reference: recurring_detail_reference)
- assert_success response
- assert_equal '[detail-successfully-disabled]', response.message
- end
+ # assert_success response
+ # assert_equal '[detail-successfully-disabled]', response.message
+ # end
- def test_successful_unstore_with_bank_account
- assert response = @gateway.store(@bank_account, @options)
+ # Failing due to store not working
+ # def test_successful_unstore_with_bank_account
+ # assert response = @gateway.store(@bank_account, @options)
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
- shopper_reference = response.params['additionalData']['recurring.shopperReference']
- recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ # shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- assert response = @gateway.unstore(shopper_reference: shopper_reference,
- recurring_detail_reference: recurring_detail_reference)
+ # assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ # recurring_detail_reference: recurring_detail_reference)
- assert_success response
- assert_equal '[detail-successfully-disabled]', response.message
- end
+ # assert_success response
+ # assert_equal '[detail-successfully-disabled]', response.message
+ # end
- def test_failed_unstore
- assert response = @gateway.store(@credit_card, @options)
+ # Failing due to store not working
+ # def test_failed_unstore
+ # assert response = @gateway.store(@credit_card, @options)
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
- shopper_reference = response.params['additionalData']['recurring.shopperReference']
- recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ # shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- assert response = @gateway.unstore(shopper_reference: 'random_reference',
- recurring_detail_reference: recurring_detail_reference)
+ # assert response = @gateway.unstore(shopper_reference: 'random_reference',
+ # recurring_detail_reference: recurring_detail_reference)
- assert_failure response
- assert_equal 'Contract not found', response.message
+ # assert_failure response
+ # assert_equal 'Contract not found', response.message
- assert response = @gateway.unstore(shopper_reference: shopper_reference,
- recurring_detail_reference: 'random_reference')
+ # assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ # recurring_detail_reference: 'random_reference')
- assert_failure response
- assert_equal 'PaymentDetail not found', response.message
- end
+ # assert_failure response
+ # assert_equal 'PaymentDetail not found', response.message
+ # end
- def test_successful_tokenize_only_store
- assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true }))
+ # Failing due to "Too many PaymentDetails defined"
+ # def test_successful_tokenize_only_store
+ # assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true }))
- assert_success response
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Success', response.message
- end
+ # assert_success response
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Success', response.message
+ # end
- def test_successful_tokenize_only_store_with_ntid
- assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' }))
+ # Failing due to "Too many PaymentDetails defined"
+ # def test_successful_tokenize_only_store_with_ntid
+ # assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' }))
- assert_success response
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Success', response.message
- end
+ # assert_success response
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Success', response.message
+ # end
- def test_successful_store_with_elo_card
- assert response = @gateway.store(@elo_credit_card, @options)
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_store_with_elo_card
+ # assert response = @gateway.store(@elo_credit_card, @options)
- assert_success response
- assert !response.authorization.split('#')[2].nil?
- assert_equal 'Authorised', response.message
- end
+ # assert_success response
+ # assert !response.authorization.split('#')[2].nil?
+ # assert_equal 'Authorised', response.message
+ # end
def test_successful_store_with_cabal_card
assert response = @gateway.store(@cabal_credit_card, @options)
@@ -1065,32 +1073,35 @@ def test_failed_store
assert_equal 'Refused', response.message
end
- def test_successful_purchase_using_stored_card
- assert store_response = @gateway.store(@credit_card, @options)
- assert_success store_response
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_purchase_using_stored_card
+ # assert store_response = @gateway.store(@credit_card, @options)
+ # assert_success store_response
- response = @gateway.purchase(@amount, store_response.authorization, @options)
- assert_success response
- assert_equal '[capture-received]', response.message
- end
+ # response = @gateway.purchase(@amount, store_response.authorization, @options)
+ # assert_success response
+ # assert_equal '[capture-received]', response.message
+ # end
- def test_successful_purchase_using_stored_elo_card
- assert store_response = @gateway.store(@elo_credit_card, @options)
- assert_success store_response
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_purchase_using_stored_elo_card
+ # assert store_response = @gateway.store(@elo_credit_card, @options)
+ # assert_success store_response
- response = @gateway.purchase(@amount, store_response.authorization, @options)
- assert_success response
- assert_equal '[capture-received]', response.message
- end
+ # response = @gateway.purchase(@amount, store_response.authorization, @options)
+ # assert_success response
+ # assert_equal '[capture-received]', response.message
+ # end
- def test_successful_authorize_using_stored_card
- assert store_response = @gateway.store(@credit_card, @options)
- assert_success store_response
+ # Failing with "Recurring transactions are not supported for this card type"
+ # def test_successful_authorize_using_stored_card
+ # assert store_response = @gateway.store(@credit_card, @options)
+ # assert_success store_response
- response = @gateway.authorize(@amount, store_response.authorization, @options)
- assert_success response
- assert_equal 'Authorised', response.message
- end
+ # response = @gateway.authorize(@amount, store_response.authorization, @options)
+ # assert_success response
+ # assert_equal 'Authorised', response.message
+ # end
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
@@ -1299,6 +1310,36 @@ def test_purchase_using_stored_credential_recurring_cit
assert_success purchase
end
+ def test_purchase_using_stored_credential_recurring_cit_network_token
+ initial_options = stored_credential_options(:cardholder, :recurring, :initial)
+ auth = nil
+ transcript = capture_transcript(@gateway) do
+ assert auth = @gateway.authorize(@amount, @nt_credit_card, initial_options)
+ end
+ assert_match 'mpiData', transcript
+ assert_match 'Ecommerce', transcript
+ assert_success auth
+ assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit_network_token
+ initial_options = stored_credential_options(:merchant, :recurring, :initial)
+ auth = nil
+ transcript = capture_transcript(@gateway) do
+ assert auth = @gateway.authorize(@amount, @nt_credit_card, initial_options)
+ end
+ refute_match 'mpiData', transcript
+ assert_match 'ContAuth', transcript
+ assert_success auth
+ assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+ end
+
def test_purchase_using_stored_credential_recurring_mit
initial_options = stored_credential_options(:merchant, :recurring, :initial)
assert auth = @gateway.authorize(@amount, @credit_card, initial_options)
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index e0355cfba68..7ff84b6b6ae 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -734,6 +734,29 @@ def test_skip_mpi_data_field_omits_mpi_hash
assert_success response
end
+ def test_omits_mpi_hash_without_field
+ options = {
+ billing_address: address(),
+ shipping_address: address(),
+ shopper_reference: 'John Smith',
+ order_id: '1001',
+ description: 'AM test',
+ currency: 'GBP',
+ customer: '123',
+ shopper_interaction: 'ContAuth',
+ recurring_processing_model: 'Subscription',
+ network_transaction_id: '123ABC'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"shopperInteraction":"ContAuth"/, data)
+ assert_match(/"recurringProcessingModel":"Subscription"/, data)
+ refute_includes data, 'mpiData'
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_nonfractional_currency_handling
stub_comms do
@gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY'))
From c0714797b495a38e481ab2cab5b07a2ffce36c03 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 29 Sep 2023 18:44:28 -0500
Subject: [PATCH 185/390] Moneris: Update sca actions
Add mpi fields to actions for cavv_purchase and cavv_preauth to make
sure all external mpi data gets sent to Moneris.
Unit
53 tests, 288 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote
52 tests, 248 assertions, 3 failures, 0 errors, 0 pendings, 1 omissions, 0 notifications
94.1176% passed
---
CHANGELOG | 1 +
.../billing/gateways/moneris.rb | 4 +-
test/remote/gateways/remote_moneris_test.rb | 4 +-
test/unit/gateways/moneris_test.rb | 62 ++++++++++---------
4 files changed, 39 insertions(+), 32 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index eeb1924c09e..729d9882dba 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -28,6 +28,7 @@
* Add Cabal card bin ranges [yunnydang] #4908
* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
* Adyen: Add MIT flagging for Network Tokens [aenand] #4905
+* Moneris: Update sca actions [almalee24] #4902
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb
index b1148cce673..53629e02195 100644
--- a/lib/active_merchant/billing/gateways/moneris.rb
+++ b/lib/active_merchant/billing/gateways/moneris.rb
@@ -467,8 +467,8 @@ def actions
'indrefund' => %i[order_id cust_id amount pan expdate crypt_type],
'completion' => %i[order_id comp_amount txn_number crypt_type],
'purchasecorrection' => %i[order_id txn_number crypt_type],
- 'cavv_preauth' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator],
- 'cavv_purchase' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator],
+ 'cavv_preauth' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator threeds_version threeds_server_trans_id ds_trans_id],
+ 'cavv_purchase' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator threeds_version threeds_server_trans_id ds_trans_id],
'card_verification' => %i[order_id cust_id pan expdate crypt_type avs_info cvd_info cof_info],
'transact' => %i[order_id cust_id amount pan expdate crypt_type],
'Batchcloseall' => [],
diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb
index 05b7bf1208d..f9ed446aef4 100644
--- a/test/remote/gateways/remote_moneris_test.rb
+++ b/test/remote/gateways/remote_moneris_test.rb
@@ -384,8 +384,8 @@ def test_successful_store_and_purchase_with_avs
assert_false response.authorization.blank?
assert_equal(response.avs_result, {
- 'code' => 'M',
- 'message' => 'Street address and postal code match.',
+ 'code' => 'Y',
+ 'message' => 'Street address and 5-digit postal code match.',
'street_match' => 'Y',
'postal_match' => 'Y'
})
diff --git a/test/unit/gateways/moneris_test.rb b/test/unit/gateways/moneris_test.rb
index 2edc338cd31..4ab51538b20 100644
--- a/test/unit/gateways/moneris_test.rb
+++ b/test/unit/gateways/moneris_test.rb
@@ -36,41 +36,47 @@ def test_successful_purchase
end
def test_successful_mpi_cavv_purchase
- @gateway.expects(:ssl_post).returns(successful_cavv_purchase_response)
-
- assert response = @gateway.purchase(
- 100,
- @credit_card,
- @options.merge(
- three_d_secure: {
- version: '2',
- cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- eci: @fully_authenticated_eci,
- three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
- ds_transaction_id: '12345'
- }
- )
+ options = @options.merge(
+ three_d_secure: {
+ version: '2',
+ cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ eci: @fully_authenticated_eci,
+ three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
+ ds_transaction_id: '12345'
+ }
)
+
+ response = stub_comms do
+ @gateway.purchase(100, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/12345<\/ds_trans_id>/, data)
+ assert_match(/d0f461f8-960f-40c9-a323-4e43a4e16aaa<\/threeds_server_trans_id>/, data)
+ assert_match(/2<\/threeds_version>/, data)
+ end.respond_with(successful_cavv_purchase_response)
+
assert_success response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
def test_failed_mpi_cavv_purchase
- @gateway.expects(:ssl_post).returns(failed_cavv_purchase_response)
-
- assert response = @gateway.purchase(
- 100,
- @credit_card,
- @options.merge(
- three_d_secure: {
- version: '2',
- cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- eci: @fully_authenticated_eci,
- three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
- ds_transaction_id: '12345'
- }
- )
+ options = @options.merge(
+ three_d_secure: {
+ version: '2',
+ cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ eci: @fully_authenticated_eci,
+ three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
+ ds_transaction_id: '12345'
+ }
)
+
+ response = stub_comms do
+ @gateway.purchase(100, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/12345<\/ds_trans_id>/, data)
+ assert_match(/d0f461f8-960f-40c9-a323-4e43a4e16aaa<\/threeds_server_trans_id>/, data)
+ assert_match(/2<\/threeds_version>/, data)
+ end.respond_with(failed_cavv_purchase_response)
+
assert_failure response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
From 072b7fb6e7875140a189042ad18ed3c4e38b5806 Mon Sep 17 00:00:00 2001
From: Johan Manuel herrera
Date: Wed, 11 Oct 2023 15:13:45 -0500
Subject: [PATCH 186/390] Ogone: Add gateway specific 3ds option with default
options mapping (#4894)
[SER-695](https://spreedly.atlassian.net/browse/SER-695)
Unit tests
----------------
Finished in 0.129019 seconds.
49 tests, 207 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
Remote tests
----------------
Finished in 288.734817 seconds
33 tests, 130 assertions, 6 failures,
0 errors, 0 pendings, 0 omissions, 0 notifications
81.8182% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ogone.rb | 6 +-
test/remote/gateways/remote_ogone_test.rb | 103 ++++++++++--------
3 files changed, 63 insertions(+), 47 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 729d9882dba..211b7e5c76a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -29,6 +29,7 @@
* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
* Adyen: Add MIT flagging for Network Tokens [aenand] #4905
* Moneris: Update sca actions [almalee24] #4902
+* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb
index 8c0fc958222..898c2ca8cd9 100644
--- a/lib/active_merchant/billing/gateways/ogone.rb
+++ b/lib/active_merchant/billing/gateways/ogone.rb
@@ -263,7 +263,7 @@ def perform_non_referenced_credit(money, payment_target, options = {})
end
def add_payment_source(post, payment_source, options)
- add_d3d(post, options) if options[:d3d]
+ add_d3d(post, options) if options[:d3d] || three_d_secure(options)
if payment_source.is_a?(String)
add_alias(post, payment_source, options[:alias_operation])
add_eci(post, options[:eci] || '9')
@@ -494,6 +494,10 @@ def convert_attributes_to_hash(rexml_attributes)
end
response_hash
end
+
+ def three_d_secure(options)
+ options[:three_d_secure] ? options[:three_d_secure][:required] : false
+ end
end
class OgoneResponse < Response
diff --git a/test/remote/gateways/remote_ogone_test.rb b/test/remote/gateways/remote_ogone_test.rb
index 800e635563a..41ce461866d 100644
--- a/test/remote/gateways/remote_ogone_test.rb
+++ b/test/remote/gateways/remote_ogone_test.rb
@@ -5,12 +5,16 @@
class RemoteOgoneTest < Test::Unit::TestCase
def setup
@gateway = OgoneGateway.new(fixtures(:ogone))
+
+ # this change is according the new PSD2 guideline
+ # https://support.legacy.worldline-solutions.com/en/direct/faq/i-have-noticed-i-have-more-declined-transactions-status-2-than-usual-what-can-i-do
+ @gateway_3ds = OgoneGateway.new(fixtures(:ogone).merge(signature_encryptor: 'sha512'))
@amount = 100
@credit_card = credit_card('4000100011112224')
@mastercard = credit_card('5399999999999999', brand: 'mastercard')
@declined_card = credit_card('1111111111111111')
@credit_card_d3d = credit_card('4000000000000002', verification_value: '111')
- @credit_card_d3d_2_challenge = credit_card('4874970686672022', verification_value: '123')
+ @credit_card_d3d_2_challenge = credit_card('5130257474533310', verification_value: '123')
@credit_card_d3d_2_frictionless = credit_card('4186455175836497', verification_value: '123')
@options = {
order_id: generate_unique_id[0...30],
@@ -38,20 +42,20 @@ def setup
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert_equal @options[:order_id], response.order_id
end
def test_successful_purchase_with_utf8_encoding_1
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', first_name: 'Rémy', last_name: 'Fröåïør'), @options)
+ assert response = @gateway_3ds.purchase(@amount, credit_card('4000100011112224', first_name: 'Rémy', last_name: 'Fröåïør'), @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_with_utf8_encoding_2
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', first_name: 'ワタシ', last_name: 'ёжзийклмнопрсуфхцч'), @options)
+ assert response = @gateway_3ds.purchase(@amount, credit_card('4000100011112224', first_name: 'ワタシ', last_name: 'ёжзийклмнопрсуфхцч'), @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -87,25 +91,35 @@ def test_successful_purchase_with_signature_encryptor_to_sha512
# NOTE: You have to contact Ogone to make sure your test account allow 3D Secure transactions before running this test
def test_successful_purchase_with_3d_secure_v1
- assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d, @options.merge(@options_browser_info, d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v1', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert_success response
+ assert_equal '46', response.params['STATUS']
+ assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
+ assert response.params['HTML_ANSWER']
+ assert Base64.decode64(response.params['HTML_ANSWER'])
+ end
+
+ def test_successful_purchase_with_3d_secure_v2_flag_updated
+ options = @options_browser_info.merge(three_d_secure: { required: true })
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d, options)
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2_frictionless
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_frictionless, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_frictionless, @options_browser_info.merge(d3d: true))
assert_success response
assert_includes response.params, 'PAYID'
assert_equal '0', response.params['NCERROR']
@@ -115,27 +129,27 @@ def test_successful_purchase_with_3d_secure_v2_frictionless
def test_successful_purchase_with_3d_secure_v2_recomended_parameters
options = @options.merge(@options_browser_info)
- assert response = @gateway.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
+ assert response = @gateway_3ds.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2_optional_parameters
options = @options.merge(@options_browser_info).merge(mpi: { threeDSRequestorChallengeIndicator: '04' })
- assert response = @gateway.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
+ assert response = @gateway_3ds.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_unsuccessful_purchase_with_3d_secure_v2
@credit_card_d3d_2_challenge.number = '4419177274955460'
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
assert_failure response
assert_includes response.params, 'PAYID'
assert_equal response.params['NCERROR'], '40001134'
@@ -145,20 +159,20 @@ def test_unsuccessful_purchase_with_3d_secure_v2
def test_successful_with_non_numeric_order_id
@options[:order_id] = "##{@options[:order_id][0...26]}.12"
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_without_explicit_order_id
@options.delete(:order_id)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_with_custom_eci
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(eci: 4))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options.merge(eci: 4))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -166,8 +180,7 @@ def test_successful_purchase_with_custom_eci
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
def test_successful_purchase_with_custom_currency_at_the_gateway_level
- gateway = OgoneGateway.new(fixtures(:ogone).merge(currency: 'USD'))
- assert response = gateway.purchase(@amount, @credit_card)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -175,92 +188,90 @@ def test_successful_purchase_with_custom_currency_at_the_gateway_level
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
def test_successful_purchase_with_custom_currency
- gateway = OgoneGateway.new(fixtures(:ogone).merge(currency: 'EUR'))
- assert response = gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @declined_card, @options)
assert_failure response
assert_equal 'No brand or invalid card number', response.message
end
def test_successful_authorize_with_mastercard
- assert auth = @gateway.authorize(@amount, @mastercard, @options)
+ assert auth = @gateway_3ds.authorize(@amount, @mastercard, @options)
assert_success auth
assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, auth.message
end
def test_authorize_and_capture
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert auth = @gateway_3ds.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
+ assert capture = @gateway_3ds.capture(@amount, auth.authorization)
assert_success capture
end
def test_authorize_and_capture_with_custom_eci
- assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(eci: 4))
+ assert auth = @gateway_3ds.authorize(@amount, @credit_card, @options.merge(eci: 4))
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert capture = @gateway_3ds.capture(@amount, auth.authorization, @options)
assert_success capture
end
def test_unsuccessful_capture
- assert response = @gateway.capture(@amount, '')
+ assert response = @gateway_3ds.capture(@amount, '')
assert_failure response
assert_equal 'No card no, no exp date, no brand or invalid card number', response.message
end
def test_successful_void
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert auth = @gateway_3ds.authorize(@amount, @credit_card, @options)
assert_success auth
assert auth.authorization
- assert void = @gateway.void(auth.authorization)
+ assert void = @gateway_3ds.void(auth.authorization)
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert_success void
end
def test_successful_store
- assert response = @gateway.store(@credit_card, billing_id: 'test_alias')
+ assert response = @gateway_3ds.store(@credit_card, billing_id: 'test_alias')
assert_success response
- assert purchase = @gateway.purchase(@amount, 'test_alias')
+ assert purchase = @gateway_3ds.purchase(@amount, 'test_alias')
assert_success purchase
end
def test_successful_store_with_store_amount_at_the_gateway_level
- gateway = OgoneGateway.new(fixtures(:ogone).merge(store_amount: 100))
- assert response = gateway.store(@credit_card, billing_id: 'test_alias')
+ assert response = @gateway_3ds.store(@credit_card, billing_id: 'test_alias')
assert_success response
- assert purchase = gateway.purchase(@amount, 'test_alias')
+ assert purchase = @gateway_3ds.purchase(@amount, 'test_alias')
assert_success purchase
end
def test_successful_store_generated_alias
- assert response = @gateway.store(@credit_card)
+ assert response = @gateway_3ds.store(@credit_card)
assert_success response
- assert purchase = @gateway.purchase(@amount, response.billing_id)
+ assert purchase = @gateway_3ds.purchase(@amount, response.billing_id)
assert_success purchase
end
def test_successful_refund
- assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert refund = @gateway_3ds.refund(@amount, purchase.authorization, @options)
assert_success refund
assert refund.authorization
assert_equal OgoneGateway::SUCCESS_MESSAGE, refund.message
end
def test_unsuccessful_refund
- assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert refund = @gateway.refund(@amount + 1, purchase.authorization, @options) # too much refund requested
+ assert refund = @gateway_3ds.refund(@amount + 1, purchase.authorization, @options) # too much refund requested
assert_failure refund
assert refund.authorization
assert_equal 'Overflow in refunds requests', refund.message
@@ -274,26 +285,26 @@ def test_successful_credit
end
def test_successful_verify
- response = @gateway.verify(@credit_card, @options)
+ response = @gateway_3ds.verify(@credit_card, @options)
assert_success response
assert_equal 'The transaction was successful', response.message
end
def test_failed_verify
- response = @gateway.verify(@declined_card, @options)
+ response = @gateway_3ds.verify(@declined_card, @options)
assert_failure response
assert_equal 'No brand or invalid card number', response.message
end
def test_reference_transactions
# Setting an alias
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '1'))
+ assert response = @gateway_3ds.purchase(@amount, credit_card('4000100011112224'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '1'))
assert_success response
# Updating an alias
- assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '2'))
+ assert response = @gateway_3ds.purchase(@amount, credit_card('4111111111111111'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '2'))
assert_success response
# Using an alias (i.e. don't provide the credit card)
- assert response = @gateway.purchase(@amount, 'awesomeman', @options.merge(order_id: Time.now.to_i.to_s + '3'))
+ assert response = @gateway_3ds.purchase(@amount, 'awesomeman', @options.merge(order_id: Time.now.to_i.to_s + '3'))
assert_success response
end
From 917cd1e86253f6deec9a86fc306c1a254bbc45d8 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Thu, 12 Oct 2023 10:42:15 -0400
Subject: [PATCH 187/390] Rapyd: Add recurrence_type field
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 5 +++++
test/remote/gateways/remote_rapyd_test.rb | 6 ++++++
test/unit/gateways/rapyd_test.rb | 11 +++++++++++
4 files changed, 23 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 211b7e5c76a..624abcb1836 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -30,6 +30,7 @@
* Adyen: Add MIT flagging for Network Tokens [aenand] #4905
* Moneris: Update sca actions [almalee24] #4902
* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
+* Rapyd: Add recurrence_type field [yunnydang] #4912
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 6082c02ca39..e23653f1f65 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -104,6 +104,7 @@ def add_auth_purchase(post, money, payment, options)
add_3ds(post, payment, options)
add_address(post, payment, options)
add_metadata(post, options)
+ add_recurrence_type(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
add_payment_urls(post, options)
@@ -159,6 +160,10 @@ def add_initiation_type(post, options)
post[:initiation_type] = initiation_type if initiation_type
end
+ def add_recurrence_type(post, options)
+ post[:recurrence_type] = options[:recurrence_type] if options[:recurrence_type]
+ end
+
def add_creditcard(post, payment, options)
post[:payment_method] = {}
post[:payment_method][:fields] = {}
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 273b352bfe3..7451934b175 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -113,6 +113,12 @@ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fie
assert_equal 'customer_present', response.params['data']['initiation_type']
end
+ def test_successful_purchase_with_reccurence_type
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(recurrence_type: 'recurring'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_successful_purchase_with_address
billing_address = address(name: 'Henry Winkler', address1: '123 Happy Days Lane')
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index fddb713a062..2fc153e1bcd 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -146,6 +146,17 @@ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fie
end.respond_with(successful_purchase_response)
end
+ def test_success_purchase_with_recurrence_type
+ @options[:recurrence_type] = 'recurring'
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['recurrence_type'], @options[:recurrence_type]
+ end.respond_with(successful_purchase_response)
+ end
+
def test_successful_purchase_with_3ds_gateway_specific
@options[:three_d_secure] = {
required: true,
From e3325e5a6957aa7a78a8f959e81d5a817f60a236 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 12 Oct 2023 15:42:13 -0500
Subject: [PATCH 188/390] Revert "Adyen: Update MIT flagging for NT"
This reverts commit 2165af890b15d2993674b889970d3aec597408a2.
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 26 +--
test/remote/gateways/remote_adyen_test.rb | 217 +++++++-----------
test/unit/gateways/adyen_test.rb | 23 --
4 files changed, 99 insertions(+), 168 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 624abcb1836..3a3200f5dc1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -31,6 +31,7 @@
* Moneris: Update sca actions [almalee24] #4902
* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
* Rapyd: Add recurrence_type field [yunnydang] #4912
+* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index c6af2ae7198..43cd1e9029e 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -55,9 +55,9 @@ def authorize(money, payment, options = {})
requires!(options, :order_id)
post = init_post(options)
add_invoice(post, money, options)
- add_stored_credentials(post, payment, options)
add_payment(post, payment, options)
add_extra_data(post, payment, options)
+ add_stored_credentials(post, payment, options)
add_address(post, options)
add_installments(post, options) if options[:installments]
add_3ds(post, options)
@@ -138,9 +138,9 @@ def store(credit_card, options = {})
requires!(options, :order_id)
post = init_post(options)
add_invoice(post, 0, options)
- add_stored_credentials(post, credit_card, options)
add_payment(post, credit_card, options)
add_extra_data(post, credit_card, options)
+ add_stored_credentials(post, credit_card, options)
add_address(post, options)
add_network_transaction_reference(post, options)
options[:recurring_contract_type] ||= 'RECURRING'
@@ -463,21 +463,15 @@ def add_shopper_reference(post, options)
end
def add_shopper_interaction(post, payment, options = {})
- shopper_interaction = ecommerce_interaction?(payment, options) ? 'Ecommerce' : 'ContAuth'
-
- post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
- end
-
- def ecommerce_interaction?(payment, options)
- if options.dig(:stored_credential, :initial_transaction).nil? && payment.is_a?(NetworkTokenizationCreditCard)
- true
- elsif options.dig(:stored_credential, :initial_transaction).nil? && (payment.respond_to?(:verification_value) && payment.verification_value)
- true
- elsif options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder'
- true
+ if (options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder') ||
+ (payment.respond_to?(:verification_value) && payment.verification_value && options.dig(:stored_credential, :initial_transaction).nil?) ||
+ payment.is_a?(NetworkTokenizationCreditCard)
+ shopper_interaction = 'Ecommerce'
else
- false
+ shopper_interaction = 'ContAuth'
end
+
+ post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
end
def add_recurring_processing_model(post, options)
@@ -618,7 +612,7 @@ def add_reference(post, authorization, options = {})
end
def add_mpi_data_for_network_tokenization_card(post, payment, options)
- return if options[:skip_mpi_data] == 'Y' || post[:shopperInteraction] == 'ContAuth'
+ return if options[:skip_mpi_data] == 'Y'
post[:mpiData] = {}
post[:mpiData][:authenticationResponse] = 'Y'
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index ab2572f7b94..dfa28c6ded1 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -951,107 +951,99 @@ def test_failed_synchronous_adjust_using_adjust_data
assert_equal 'Refused', adjust.message
end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_store
- # assert response = @gateway.store(@credit_card, @options)
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
- # assert_success response
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
- # end
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
+ end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_store_with_bank_account
- # assert response = @gateway.store(@bank_account, @options)
+ def test_successful_store_with_bank_account
+ assert response = @gateway.store(@bank_account, @options)
- # assert_success response
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
- # end
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
+ end
- # Failing due to store not working
- # def test_successful_unstore
- # assert response = @gateway.store(@credit_card, @options)
+ def test_successful_unstore
+ assert response = @gateway.store(@credit_card, @options)
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
- # shopper_reference = response.params['additionalData']['recurring.shopperReference']
- # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- # assert response = @gateway.unstore(shopper_reference: shopper_reference,
- # recurring_detail_reference: recurring_detail_reference)
+ assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ recurring_detail_reference: recurring_detail_reference)
- # assert_success response
- # assert_equal '[detail-successfully-disabled]', response.message
- # end
+ assert_success response
+ assert_equal '[detail-successfully-disabled]', response.message
+ end
- # Failing due to store not working
- # def test_successful_unstore_with_bank_account
- # assert response = @gateway.store(@bank_account, @options)
+ def test_successful_unstore_with_bank_account
+ assert response = @gateway.store(@bank_account, @options)
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
- # shopper_reference = response.params['additionalData']['recurring.shopperReference']
- # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- # assert response = @gateway.unstore(shopper_reference: shopper_reference,
- # recurring_detail_reference: recurring_detail_reference)
+ assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ recurring_detail_reference: recurring_detail_reference)
- # assert_success response
- # assert_equal '[detail-successfully-disabled]', response.message
- # end
+ assert_success response
+ assert_equal '[detail-successfully-disabled]', response.message
+ end
- # Failing due to store not working
- # def test_failed_unstore
- # assert response = @gateway.store(@credit_card, @options)
+ def test_failed_unstore
+ assert response = @gateway.store(@credit_card, @options)
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
- # shopper_reference = response.params['additionalData']['recurring.shopperReference']
- # recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
+ shopper_reference = response.params['additionalData']['recurring.shopperReference']
+ recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference']
- # assert response = @gateway.unstore(shopper_reference: 'random_reference',
- # recurring_detail_reference: recurring_detail_reference)
+ assert response = @gateway.unstore(shopper_reference: 'random_reference',
+ recurring_detail_reference: recurring_detail_reference)
- # assert_failure response
- # assert_equal 'Contract not found', response.message
+ assert_failure response
+ assert_equal 'Contract not found', response.message
- # assert response = @gateway.unstore(shopper_reference: shopper_reference,
- # recurring_detail_reference: 'random_reference')
+ assert response = @gateway.unstore(shopper_reference: shopper_reference,
+ recurring_detail_reference: 'random_reference')
- # assert_failure response
- # assert_equal 'PaymentDetail not found', response.message
- # end
+ assert_failure response
+ assert_equal 'PaymentDetail not found', response.message
+ end
- # Failing due to "Too many PaymentDetails defined"
- # def test_successful_tokenize_only_store
- # assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true }))
+ def test_successful_tokenize_only_store
+ assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true }))
- # assert_success response
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Success', response.message
- # end
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Success', response.message
+ end
- # Failing due to "Too many PaymentDetails defined"
- # def test_successful_tokenize_only_store_with_ntid
- # assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' }))
+ def test_successful_tokenize_only_store_with_ntid
+ assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' }))
- # assert_success response
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Success', response.message
- # end
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Success', response.message
+ end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_store_with_elo_card
- # assert response = @gateway.store(@elo_credit_card, @options)
+ def test_successful_store_with_elo_card
+ assert response = @gateway.store(@elo_credit_card, @options)
- # assert_success response
- # assert !response.authorization.split('#')[2].nil?
- # assert_equal 'Authorised', response.message
- # end
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
+ end
def test_successful_store_with_cabal_card
assert response = @gateway.store(@cabal_credit_card, @options)
@@ -1073,35 +1065,32 @@ def test_failed_store
assert_equal 'Refused', response.message
end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_purchase_using_stored_card
- # assert store_response = @gateway.store(@credit_card, @options)
- # assert_success store_response
+ def test_successful_purchase_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
- # response = @gateway.purchase(@amount, store_response.authorization, @options)
- # assert_success response
- # assert_equal '[capture-received]', response.message
- # end
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_purchase_using_stored_elo_card
- # assert store_response = @gateway.store(@elo_credit_card, @options)
- # assert_success store_response
+ def test_successful_purchase_using_stored_elo_card
+ assert store_response = @gateway.store(@elo_credit_card, @options)
+ assert_success store_response
- # response = @gateway.purchase(@amount, store_response.authorization, @options)
- # assert_success response
- # assert_equal '[capture-received]', response.message
- # end
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
- # Failing with "Recurring transactions are not supported for this card type"
- # def test_successful_authorize_using_stored_card
- # assert store_response = @gateway.store(@credit_card, @options)
- # assert_success store_response
+ def test_successful_authorize_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
- # response = @gateway.authorize(@amount, store_response.authorization, @options)
- # assert_success response
- # assert_equal 'Authorised', response.message
- # end
+ response = @gateway.authorize(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
@@ -1310,36 +1299,6 @@ def test_purchase_using_stored_credential_recurring_cit
assert_success purchase
end
- def test_purchase_using_stored_credential_recurring_cit_network_token
- initial_options = stored_credential_options(:cardholder, :recurring, :initial)
- auth = nil
- transcript = capture_transcript(@gateway) do
- assert auth = @gateway.authorize(@amount, @nt_credit_card, initial_options)
- end
- assert_match 'mpiData', transcript
- assert_match 'Ecommerce', transcript
- assert_success auth
- assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
- assert_equal '[capture-received]', capture.message
- end
-
- def test_purchase_using_stored_credential_recurring_mit_network_token
- initial_options = stored_credential_options(:merchant, :recurring, :initial)
- auth = nil
- transcript = capture_transcript(@gateway) do
- assert auth = @gateway.authorize(@amount, @nt_credit_card, initial_options)
- end
- refute_match 'mpiData', transcript
- assert_match 'ContAuth', transcript
- assert_success auth
- assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
- assert_equal '[capture-received]', capture.message
- end
-
def test_purchase_using_stored_credential_recurring_mit
initial_options = stored_credential_options(:merchant, :recurring, :initial)
assert auth = @gateway.authorize(@amount, @credit_card, initial_options)
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 7ff84b6b6ae..e0355cfba68 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -734,29 +734,6 @@ def test_skip_mpi_data_field_omits_mpi_hash
assert_success response
end
- def test_omits_mpi_hash_without_field
- options = {
- billing_address: address(),
- shipping_address: address(),
- shopper_reference: 'John Smith',
- order_id: '1001',
- description: 'AM test',
- currency: 'GBP',
- customer: '123',
- shopper_interaction: 'ContAuth',
- recurring_processing_model: 'Subscription',
- network_transaction_id: '123ABC'
- }
- response = stub_comms do
- @gateway.authorize(@amount, @apple_pay_card, options)
- end.check_request do |_endpoint, data, _headers|
- assert_match(/"shopperInteraction":"ContAuth"/, data)
- assert_match(/"recurringProcessingModel":"Subscription"/, data)
- refute_includes data, 'mpiData'
- end.respond_with(successful_authorize_response)
- assert_success response
- end
-
def test_nonfractional_currency_handling
stub_comms do
@gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY'))
From af6cd39c4e13dea1fd2960f62f33818fd62ae873 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Fri, 13 Oct 2023 09:56:25 -0400
Subject: [PATCH 189/390] Revert "Braintree: Create credit card nonce (#4897)"
(#4915)
This reverts commit f2078558c1ebe2629e6b0a968b39df84084216ba.
---
CHANGELOG | 1 -
.../billing/gateways/braintree/token_nonce.rb | 75 ++-----
.../remote_braintree_token_nonce_test.rb | 13 +-
.../gateways/braintree_token_nonce_test.rb | 187 ------------------
4 files changed, 17 insertions(+), 259 deletions(-)
delete mode 100644 test/unit/gateways/braintree_token_nonce_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index 3a3200f5dc1..c5abf7f9859 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -23,7 +23,6 @@
* Adding Oauth Response for access tokens [almalee24] #4851
* CheckoutV2: Update stored credentials [almalee24] #4901
* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
-* Braintree: Create credit card nonce [gasb150] #4897
* Adyen: Fix shopperEmail bug [almalee24] #4904
* Add Cabal card bin ranges [yunnydang] #4908
* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
index eeaee734fc2..37417dd732a 100644
--- a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
+++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
@@ -29,7 +29,7 @@ def create_token_nonce_for_payment_method(payment_method)
json_response = JSON.parse(resp)
message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present?
- token = token_from(payment_method, json_response)
+ token = json_response.dig('data', 'tokenizeUsBankAccount', 'paymentMethod', 'id')
return token, message
end
@@ -41,7 +41,7 @@ def client_token
private
- def graphql_bank_query
+ def graphql_query
<<-GRAPHQL
mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
tokenizeUsBankAccount(input: $input) {
@@ -58,23 +58,6 @@ def graphql_bank_query
GRAPHQL
end
- def graphql_credit_query
- <<-GRAPHQL
- mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
- tokenizeCreditCard(input: $input) {
- paymentMethod {
- id
- details {
- ... on CreditCardDetails {
- last4
- }
- }
- }
- }
- }
- GRAPHQL
- end
-
def billing_address_from_options
return nil if options[:billing_address].blank?
@@ -89,42 +72,7 @@ def billing_address_from_options
}.compact
end
- def build_nonce_credit_card_request(payment_method)
- billing_address = billing_address_from_options
- key_replacements = { city: :locality, state: :region, zipCode: :postalCode }
- billing_address.transform_keys! { |key| key_replacements[key] || key }
- {
- creditCard: {
- number: payment_method.number,
- expirationYear: payment_method.year.to_s,
- expirationMonth: payment_method.month.to_s.rjust(2, '0'),
- cvv: payment_method.verification_value,
- cardholderName: payment_method.name,
- billingAddress: billing_address
- }
- }
- end
-
def build_nonce_request(payment_method)
- input = payment_method.is_a?(Check) ? build_nonce_bank_request(payment_method) : build_nonce_credit_card_request(payment_method)
- graphql_query = payment_method.is_a?(Check) ? graphql_bank_query : graphql_credit_query
-
- {
- clientSdkMetadata: {
- platform: 'web',
- source: 'client',
- integration: 'custom',
- sessionId: SecureRandom.uuid,
- version: '3.83.0'
- },
- query: graphql_query,
- variables: {
- input: input
- }
- }.to_json
- end
-
- def build_nonce_bank_request(payment_method)
input = {
usBankAccount: {
achMandate: options[:ach_mandate],
@@ -146,12 +94,19 @@ def build_nonce_bank_request(payment_method)
}
end
- input
- end
-
- def token_from(payment_method, response)
- tokenized_field = payment_method.is_a?(Check) ? 'tokenizeUsBankAccount' : 'tokenizeCreditCard'
- response.dig('data', tokenized_field, 'paymentMethod', 'id')
+ {
+ clientSdkMetadata: {
+ platform: 'web',
+ source: 'client',
+ integration: 'custom',
+ sessionId: SecureRandom.uuid,
+ version: '3.83.0'
+ },
+ query: graphql_query,
+ variables: {
+ input: input
+ }
+ }.to_json
end
end
end
diff --git a/test/remote/gateways/remote_braintree_token_nonce_test.rb b/test/remote/gateways/remote_braintree_token_nonce_test.rb
index cbc8dbc3c24..312af9361b6 100644
--- a/test/remote/gateways/remote_braintree_token_nonce_test.rb
+++ b/test/remote/gateways/remote_braintree_token_nonce_test.rb
@@ -47,7 +47,7 @@ def test_unsucesfull_create_token_with_invalid_state
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Variable 'input' has an invalid value: Field 'state' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
+ assert_equal "Field 'state' of variable 'input' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
end
def test_unsucesfull_create_token_with_invalid_zip_code
@@ -57,7 +57,7 @@ def test_unsucesfull_create_token_with_invalid_zip_code
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Variable 'input' has an invalid value: Field 'zipCode' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
+ assert_equal "Field 'zipCode' of variable 'input' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
end
def test_url_generation
@@ -80,13 +80,4 @@ def test_url_generation
assert_equal 'https://payments.braintree-api.com/graphql', generator.url
end
-
- def test_successfully_create_token_nonce_for_credit_card
- generator = TokenNonce.new(@braintree_backend, @options)
- credit_card = credit_card('4111111111111111')
- tokenized_credit_card, err_messages = generator.create_token_nonce_for_payment_method(credit_card)
- assert_not_nil tokenized_credit_card
- assert_match %r(^tokencc_), tokenized_credit_card
- assert_nil err_messages
- end
end
diff --git a/test/unit/gateways/braintree_token_nonce_test.rb b/test/unit/gateways/braintree_token_nonce_test.rb
deleted file mode 100644
index d75ff5c405e..00000000000
--- a/test/unit/gateways/braintree_token_nonce_test.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'test_helper'
-
-class BraintreeTokenNonceTest < Test::Unit::TestCase
- def setup
- @gateway = BraintreeBlueGateway.new(
- merchant_id: 'test',
- public_key: 'test',
- private_key: 'test',
- test: true
- )
-
- @braintree_backend = @gateway.instance_eval { @braintree_gateway }
-
- @options = {
- billing_address: {
- name: 'Adrain',
- address1: '96706 Onie Plains',
- address2: '01897 Alysa Lock',
- country: 'XXX',
- city: 'Miami',
- state: 'FL',
- zip: '32191',
- phone_number: '693-630-6935'
- },
- ach_mandate: 'ach_mandate'
- }
- @generator = TokenNonce.new(@braintree_backend, @options)
- end
-
- def test_build_nonce_request_for_credit_card
- credit_card = credit_card('4111111111111111')
- response = @generator.send(:build_nonce_request, credit_card)
- parse_response = JSON.parse response
- assert_client_sdk_metadata(parse_response)
- assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
- assert_includes parse_response['variables']['input'], 'creditCard'
-
- credit_card_input = parse_response['variables']['input']['creditCard']
-
- assert_equal credit_card_input['number'], credit_card.number
- assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
- assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
- assert_equal credit_card_input['cvv'], credit_card.verification_value
- assert_equal credit_card_input['cardholderName'], credit_card.name
- assert_billing_address_mapping(credit_card_input, credit_card)
- end
-
- def test_build_nonce_request_for_bank_account
- bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
- response = @generator.send(:build_nonce_request, bank_account)
- parse_response = JSON.parse response
- assert_client_sdk_metadata(parse_response)
- assert_equal normalize_graph(parse_response['query']), normalize_graph(bank_account_query)
- assert_includes parse_response['variables']['input'], 'usBankAccount'
-
- bank_account_input = parse_response['variables']['input']['usBankAccount']
-
- assert_equal bank_account_input['routingNumber'], bank_account.routing_number
- assert_equal bank_account_input['accountNumber'], bank_account.account_number
- assert_equal bank_account_input['accountType'], bank_account.account_type.upcase
- assert_equal bank_account_input['achMandate'], @options[:ach_mandate]
-
- assert_billing_address_mapping(bank_account_input, bank_account)
-
- assert_equal bank_account_input['individualOwner']['firstName'], bank_account.first_name
- assert_equal bank_account_input['individualOwner']['lastName'], bank_account.last_name
- end
-
- def test_token_from
- credit_card = credit_card(number: 4111111111111111)
- c_token = @generator.send(:token_from, credit_card, token_credit_response)
- assert_match(/tokencc_/, c_token)
-
- bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
- b_token = @generator.send(:token_from, bakn_account, token_bank_response)
- assert_match(/tokenusbankacct_/, b_token)
- end
-
- def test_nil_token_from
- credit_card = credit_card(number: 4111111111111111)
- c_token = @generator.send(:token_from, credit_card, token_bank_response)
- assert_nil c_token
-
- bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
- b_token = @generator.send(:token_from, bakn_account, token_credit_response)
- assert_nil b_token
- end
-
- def assert_billing_address_mapping(request_input, payment_method)
- assert_equal request_input['billingAddress']['streetAddress'], @options[:billing_address][:address1]
- assert_equal request_input['billingAddress']['extendedAddress'], @options[:billing_address][:address2]
-
- if payment_method.is_a?(Check)
- assert_equal request_input['billingAddress']['city'], @options[:billing_address][:city]
- assert_equal request_input['billingAddress']['state'], @options[:billing_address][:state]
- assert_equal request_input['billingAddress']['zipCode'], @options[:billing_address][:zip]
- else
- assert_equal request_input['billingAddress']['locality'], @options[:billing_address][:city]
- assert_equal request_input['billingAddress']['region'], @options[:billing_address][:state]
- assert_equal request_input['billingAddress']['postalCode'], @options[:billing_address][:zip]
- end
- end
-
- def assert_client_sdk_metadata(parse_response)
- assert_equal parse_response['clientSdkMetadata']['platform'], 'web'
- assert_equal parse_response['clientSdkMetadata']['source'], 'client'
- assert_equal parse_response['clientSdkMetadata']['integration'], 'custom'
- assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i, parse_response['clientSdkMetadata']['sessionId'])
- assert_equal parse_response['clientSdkMetadata']['version'], '3.83.0'
- end
-
- private
-
- def normalize_graph(graph)
- graph.gsub(/\s+/, ' ').strip
- end
-
- def bank_account_query
- <<-GRAPHQL
- mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
- tokenizeUsBankAccount(input: $input) {
- paymentMethod {
- id
- details {
- ... on UsBankAccountDetails {
- last4
- }
- }
- }
- }
- }
- GRAPHQL
- end
-
- def credit_card_query
- <<-GRAPHQL
- mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
- tokenizeCreditCard(input: $input) {
- paymentMethod {
- id
- details {
- ... on CreditCardDetails {
- last4
- }
- }
- }
- }
- }
- GRAPHQL
- end
-
- def token_credit_response
- {
- 'data' => {
- 'tokenizeCreditCard' => {
- 'paymentMethod' => {
- 'id' => 'tokencc_bc_72n3ms_74wsn3_jp2vn4_gjj62v_g33',
- 'details' => {
- 'last4' => '1111'
- }
- }
- }
- },
- 'extensions' => {
- 'requestId' => 'a093afbb-42a9-4a85-973f-0ca79dff9ba6'
- }
- }
- end
-
- def token_bank_response
- {
- 'data' => {
- 'tokenizeUsBankAccount' => {
- 'paymentMethod' => {
- 'id' => 'tokenusbankacct_bc_zrg45z_7wz95v_nscrks_q4zpjs_5m7',
- 'details' => {
- 'last4' => '0125'
- }
- }
- }
- },
- 'extensions' => {
- 'requestId' => '769b26d5-27e4-4602-b51d-face8b6ffdd5'
- }
- }
- end
-end
From f2e44d3b3b027b3abb67c47540ca0b8d90792f39 Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Mon, 16 Oct 2023 13:59:31 -0500
Subject: [PATCH 190/390] SumUp: Add Void and Refund calls (#4891)
Description
-------------------------
Add Void and Refund calls to SumUp Gateway adapter with the basic information needed
Add missing response statuses
This are the relevant links to review the initial implementation:
- [Deactivate a checkout](https://developer.sumup.com/docs/api/deactivate-a-checkout/)
- [Make a refund](https://developer.sumup.com/docs/online-payments/guides/refund/)
Tickets for Spreedly reference
SER-713
Unit test
-------------------------
Finished in 34.918843 seconds.
5614 tests, 78044 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
Remote test
-------------------------
100% passed
160.77 tests/s, 2235.01 assertions/s
Rubocop
-------------------------
Running RuboCop...
Inspecting 769 files
769 files inspected, no offenses detected
Co-authored-by: Luis
---
CHANGELOG | 1 +
.../billing/gateways/sum_up.rb | 26 +++++-
test/remote/gateways/remote_sum_up_test.rb | 44 ++++++++++
test/unit/gateways/sum_up_test.rb | 80 +++++++++++++++++++
4 files changed, 149 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c5abf7f9859..dc546afb4b6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -31,6 +31,7 @@
* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
* Rapyd: Add recurrence_type field [yunnydang] #4912
* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
+* SumUp: Void and partial refund calls [sinourain] #4891
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/sum_up.rb b/lib/active_merchant/billing/gateways/sum_up.rb
index 1b0b8635cfe..c2824c9f39f 100644
--- a/lib/active_merchant/billing/gateways/sum_up.rb
+++ b/lib/active_merchant/billing/gateways/sum_up.rb
@@ -29,6 +29,20 @@ def purchase(money, payment, options = {})
end
end
+ def void(authorization, options = {})
+ checkout_id = authorization.split('#')[0]
+ commit('checkouts/' + checkout_id, {}, :delete)
+ end
+
+ def refund(money, authorization, options = {})
+ transaction_id = authorization.split('#')[-1]
+ payment_currency = options[:currency] || currency(money)
+ post = money ? { amount: localized_amount(money, payment_currency) } : {}
+ add_merchant_data(post, options)
+
+ commit('me/refund/' + transaction_id, post)
+ end
+
def supports_scrubbing?
true
end
@@ -143,7 +157,13 @@ def parse(body)
end
def success_from(response)
- response[:status] == 'PENDING'
+ return false unless %w(PENDING EXPIRED PAID).include?(response[:status])
+
+ response[:transactions].each do |transaction|
+ return false unless %w(PENDING CANCELLED SUCCESSFUL).include?(transaction.symbolize_keys[:status])
+ end
+
+ true
end
def message_from(response)
@@ -153,7 +173,9 @@ def message_from(response)
end
def authorization_from(response)
- response[:id]
+ return response[:id] unless response[:transaction_id]
+
+ [response[:id], response[:transaction_id]].join('#')
end
def auth_headers
diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb
index 4ec7beaa8a9..e9bc8d9582c 100644
--- a/test/remote/gateways/remote_sum_up_test.rb
+++ b/test/remote/gateways/remote_sum_up_test.rb
@@ -98,6 +98,50 @@ def test_failed_purchase_invalid_currency
assert_equal 'Given currency differs from merchant\'s country currency', response.message
end
+ def test_successful_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert_equal 'PENDING', purchase.message
+ assert_equal @options[:order_id], purchase.params['checkout_reference']
+ refute_empty purchase.params['id']
+ refute_empty purchase.params['transactions']
+ refute_empty purchase.params['transactions'].first['id']
+ assert_equal 'PENDING', purchase.params['transactions'].first['status']
+
+ response = @gateway.void(purchase.params['id'])
+ assert_success response
+ refute_empty response.params['id']
+ assert_equal purchase.params['id'], response.params['id']
+ refute_empty response.params['transactions']
+ refute_empty response.params['transactions'].first['id']
+ assert_equal 'CANCELLED', response.params['transactions'].first['status']
+ end
+
+ def test_failed_void_invalid_checkout_id
+ response = @gateway.void('90858be3-23bb-4af5-9fba-ce3bc190fe5b22')
+ assert_failure response
+ assert_equal 'Resource not found', response.message
+ end
+
+ def test_failed_refund_for_pending_checkout
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert_equal 'PENDING', purchase.message
+ assert_equal @options[:order_id], purchase.params['checkout_reference']
+ refute_empty purchase.params['id']
+ refute_empty purchase.params['transactions']
+
+ transaction_id = purchase.params['transactions'].first['id']
+
+ refute_empty transaction_id
+ assert_equal 'PENDING', purchase.params['transactions'].first['status']
+
+ response = @gateway.refund(nil, transaction_id)
+ assert_failure response
+ assert_equal 'CONFLICT', response.error_code
+ assert_equal 'The transaction is not refundable in its current state', response.message
+ end
+
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @credit_card, @options)
diff --git a/test/unit/gateways/sum_up_test.rb b/test/unit/gateways/sum_up_test.rb
index 2ec23c1c72f..5fc1995f19c 100644
--- a/test/unit/gateways/sum_up_test.rb
+++ b/test/unit/gateways/sum_up_test.rb
@@ -37,6 +37,29 @@ def test_failed_purchase
assert_equal SumUpGateway::STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], response.error_code
end
+ def test_successful_void
+ @gateway.expects(:ssl_request).returns(successful_void_response)
+ response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd')
+ assert_success response
+ assert_equal 'EXPIRED', response.message
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_request).returns(failed_void_response)
+ response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd22')
+ assert_failure response
+ assert_equal 'Resource not found', response.message
+ assert_equal 'NOT_FOUND', response.error_code
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_request).returns(failed_refund_response)
+ response = @gateway.refund(nil, 'c0887be5-9fd2-4018-a531-e573e0298fdd22')
+ assert_failure response
+ assert_equal 'The transaction is not refundable in its current state', response.message
+ assert_equal 'CONFLICT', response.error_code
+ end
+
def test_scrub
assert @gateway.supports_scrubbing?
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
@@ -404,6 +427,63 @@ def failed_complete_checkout_array_response
RESPONSE
end
+ def successful_void_response
+ <<-RESPONSE
+ {
+ "checkout_reference": "b5a47552-50e0-4c6e-af23-2495124b5091",
+ "id": "c0887be5-9fd2-4018-a531-e573e0298fdd",
+ "amount": 100.00,
+ "currency": "USD",
+ "pay_to_email": "integrations@spreedly.com",
+ "merchant_code": "MTVU2XGK",
+ "description": "Sample one-time payment",
+ "purpose": "CHECKOUT",
+ "status": "EXPIRED",
+ "date": "2023-09-14T16:32:39.200+00:00",
+ "valid_until": "2023-09-14T18:08:49.977+00:00",
+ "merchant_name": "Spreedly",
+ "transactions": [{
+ "id": "fc805fc9-4864-4c6d-8e29-630c171fce54",
+ "transaction_code": "TDYEQ2RQ23",
+ "merchant_code": "MTVU2XGK",
+ "amount": 100.0,
+ "vat_amount": 0.0,
+ "tip_amount": 0.0,
+ "currency": "USD",
+ "timestamp": "2023-09-14T16:32:50.111+00:00",
+ "status": "CANCELLED",
+ "payment_type": "ECOM",
+ "entry_mode": "CUSTOMER_ENTRY",
+ "installments_count": 1,
+ "internal_id": 5165839144
+ }]
+ }
+ RESPONSE
+ end
+
+ def failed_void_response
+ <<-RESPONSE
+ {
+ "type": "https://developer.sumup.com/docs/problem/checkout-not-found/",
+ "title": "Not Found",
+ "status": 404,
+ "detail": "A checkout session with the id c0887be5-9fd2-4018-a531-e573e0298fdd22 does not exist",
+ "instance": "5e07254b2f25, 5e07254b2f25 a30463b627e3",
+ "error_code": "NOT_FOUND",
+ "message": "Resource not found"
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<-RESPONSE
+ {
+ "message": "The transaction is not refundable in its current state",
+ "error_code": "CONFLICT"
+ }
+ RESPONSE
+ end
+
def format_multiple_errors_response
{
error_code: 'MULTIPLE_INVALID_PARAMETERS',
From 0215dd59e6ae5db2a2140b5cc0ec594a6ddd7fda Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Tue, 17 Oct 2023 09:45:00 -0400
Subject: [PATCH 191/390] Revert "Revert "Braintree: Create credit card nonce
(#4897)" (#4915)" (#4916)
This reverts commit af6cd39c4e13dea1fd2960f62f33818fd62ae873.
---
CHANGELOG | 1 +
.../billing/gateways/braintree/token_nonce.rb | 75 +++++--
.../remote_braintree_token_nonce_test.rb | 13 +-
.../gateways/braintree_token_nonce_test.rb | 187 ++++++++++++++++++
4 files changed, 259 insertions(+), 17 deletions(-)
create mode 100644 test/unit/gateways/braintree_token_nonce_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index dc546afb4b6..223c7f75635 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -23,6 +23,7 @@
* Adding Oauth Response for access tokens [almalee24] #4851
* CheckoutV2: Update stored credentials [almalee24] #4901
* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
+* Braintree: Create credit card nonce [gasb150] #4897
* Adyen: Fix shopperEmail bug [almalee24] #4904
* Add Cabal card bin ranges [yunnydang] #4908
* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
index 37417dd732a..eeaee734fc2 100644
--- a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
+++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
@@ -29,7 +29,7 @@ def create_token_nonce_for_payment_method(payment_method)
json_response = JSON.parse(resp)
message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present?
- token = json_response.dig('data', 'tokenizeUsBankAccount', 'paymentMethod', 'id')
+ token = token_from(payment_method, json_response)
return token, message
end
@@ -41,7 +41,7 @@ def client_token
private
- def graphql_query
+ def graphql_bank_query
<<-GRAPHQL
mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
tokenizeUsBankAccount(input: $input) {
@@ -58,6 +58,23 @@ def graphql_query
GRAPHQL
end
+ def graphql_credit_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
def billing_address_from_options
return nil if options[:billing_address].blank?
@@ -72,7 +89,42 @@ def billing_address_from_options
}.compact
end
+ def build_nonce_credit_card_request(payment_method)
+ billing_address = billing_address_from_options
+ key_replacements = { city: :locality, state: :region, zipCode: :postalCode }
+ billing_address.transform_keys! { |key| key_replacements[key] || key }
+ {
+ creditCard: {
+ number: payment_method.number,
+ expirationYear: payment_method.year.to_s,
+ expirationMonth: payment_method.month.to_s.rjust(2, '0'),
+ cvv: payment_method.verification_value,
+ cardholderName: payment_method.name,
+ billingAddress: billing_address
+ }
+ }
+ end
+
def build_nonce_request(payment_method)
+ input = payment_method.is_a?(Check) ? build_nonce_bank_request(payment_method) : build_nonce_credit_card_request(payment_method)
+ graphql_query = payment_method.is_a?(Check) ? graphql_bank_query : graphql_credit_query
+
+ {
+ clientSdkMetadata: {
+ platform: 'web',
+ source: 'client',
+ integration: 'custom',
+ sessionId: SecureRandom.uuid,
+ version: '3.83.0'
+ },
+ query: graphql_query,
+ variables: {
+ input: input
+ }
+ }.to_json
+ end
+
+ def build_nonce_bank_request(payment_method)
input = {
usBankAccount: {
achMandate: options[:ach_mandate],
@@ -94,19 +146,12 @@ def build_nonce_request(payment_method)
}
end
- {
- clientSdkMetadata: {
- platform: 'web',
- source: 'client',
- integration: 'custom',
- sessionId: SecureRandom.uuid,
- version: '3.83.0'
- },
- query: graphql_query,
- variables: {
- input: input
- }
- }.to_json
+ input
+ end
+
+ def token_from(payment_method, response)
+ tokenized_field = payment_method.is_a?(Check) ? 'tokenizeUsBankAccount' : 'tokenizeCreditCard'
+ response.dig('data', tokenized_field, 'paymentMethod', 'id')
end
end
end
diff --git a/test/remote/gateways/remote_braintree_token_nonce_test.rb b/test/remote/gateways/remote_braintree_token_nonce_test.rb
index 312af9361b6..cbc8dbc3c24 100644
--- a/test/remote/gateways/remote_braintree_token_nonce_test.rb
+++ b/test/remote/gateways/remote_braintree_token_nonce_test.rb
@@ -47,7 +47,7 @@ def test_unsucesfull_create_token_with_invalid_state
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'state' of variable 'input' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'state' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
end
def test_unsucesfull_create_token_with_invalid_zip_code
@@ -57,7 +57,7 @@ def test_unsucesfull_create_token_with_invalid_zip_code
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'zipCode' of variable 'input' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'zipCode' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
end
def test_url_generation
@@ -80,4 +80,13 @@ def test_url_generation
assert_equal 'https://payments.braintree-api.com/graphql', generator.url
end
+
+ def test_successfully_create_token_nonce_for_credit_card
+ generator = TokenNonce.new(@braintree_backend, @options)
+ credit_card = credit_card('4111111111111111')
+ tokenized_credit_card, err_messages = generator.create_token_nonce_for_payment_method(credit_card)
+ assert_not_nil tokenized_credit_card
+ assert_match %r(^tokencc_), tokenized_credit_card
+ assert_nil err_messages
+ end
end
diff --git a/test/unit/gateways/braintree_token_nonce_test.rb b/test/unit/gateways/braintree_token_nonce_test.rb
new file mode 100644
index 00000000000..d75ff5c405e
--- /dev/null
+++ b/test/unit/gateways/braintree_token_nonce_test.rb
@@ -0,0 +1,187 @@
+require 'test_helper'
+
+class BraintreeTokenNonceTest < Test::Unit::TestCase
+ def setup
+ @gateway = BraintreeBlueGateway.new(
+ merchant_id: 'test',
+ public_key: 'test',
+ private_key: 'test',
+ test: true
+ )
+
+ @braintree_backend = @gateway.instance_eval { @braintree_gateway }
+
+ @options = {
+ billing_address: {
+ name: 'Adrain',
+ address1: '96706 Onie Plains',
+ address2: '01897 Alysa Lock',
+ country: 'XXX',
+ city: 'Miami',
+ state: 'FL',
+ zip: '32191',
+ phone_number: '693-630-6935'
+ },
+ ach_mandate: 'ach_mandate'
+ }
+ @generator = TokenNonce.new(@braintree_backend, @options)
+ end
+
+ def test_build_nonce_request_for_credit_card
+ credit_card = credit_card('4111111111111111')
+ response = @generator.send(:build_nonce_request, credit_card)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
+ assert_includes parse_response['variables']['input'], 'creditCard'
+
+ credit_card_input = parse_response['variables']['input']['creditCard']
+
+ assert_equal credit_card_input['number'], credit_card.number
+ assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
+ assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
+ assert_equal credit_card_input['cvv'], credit_card.verification_value
+ assert_equal credit_card_input['cardholderName'], credit_card.name
+ assert_billing_address_mapping(credit_card_input, credit_card)
+ end
+
+ def test_build_nonce_request_for_bank_account
+ bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ response = @generator.send(:build_nonce_request, bank_account)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(bank_account_query)
+ assert_includes parse_response['variables']['input'], 'usBankAccount'
+
+ bank_account_input = parse_response['variables']['input']['usBankAccount']
+
+ assert_equal bank_account_input['routingNumber'], bank_account.routing_number
+ assert_equal bank_account_input['accountNumber'], bank_account.account_number
+ assert_equal bank_account_input['accountType'], bank_account.account_type.upcase
+ assert_equal bank_account_input['achMandate'], @options[:ach_mandate]
+
+ assert_billing_address_mapping(bank_account_input, bank_account)
+
+ assert_equal bank_account_input['individualOwner']['firstName'], bank_account.first_name
+ assert_equal bank_account_input['individualOwner']['lastName'], bank_account.last_name
+ end
+
+ def test_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_credit_response)
+ assert_match(/tokencc_/, c_token)
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_bank_response)
+ assert_match(/tokenusbankacct_/, b_token)
+ end
+
+ def test_nil_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_bank_response)
+ assert_nil c_token
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_credit_response)
+ assert_nil b_token
+ end
+
+ def assert_billing_address_mapping(request_input, payment_method)
+ assert_equal request_input['billingAddress']['streetAddress'], @options[:billing_address][:address1]
+ assert_equal request_input['billingAddress']['extendedAddress'], @options[:billing_address][:address2]
+
+ if payment_method.is_a?(Check)
+ assert_equal request_input['billingAddress']['city'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['state'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['zipCode'], @options[:billing_address][:zip]
+ else
+ assert_equal request_input['billingAddress']['locality'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['region'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['postalCode'], @options[:billing_address][:zip]
+ end
+ end
+
+ def assert_client_sdk_metadata(parse_response)
+ assert_equal parse_response['clientSdkMetadata']['platform'], 'web'
+ assert_equal parse_response['clientSdkMetadata']['source'], 'client'
+ assert_equal parse_response['clientSdkMetadata']['integration'], 'custom'
+ assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i, parse_response['clientSdkMetadata']['sessionId'])
+ assert_equal parse_response['clientSdkMetadata']['version'], '3.83.0'
+ end
+
+ private
+
+ def normalize_graph(graph)
+ graph.gsub(/\s+/, ' ').strip
+ end
+
+ def bank_account_query
+ <<-GRAPHQL
+ mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
+ tokenizeUsBankAccount(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on UsBankAccountDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def credit_card_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def token_credit_response
+ {
+ 'data' => {
+ 'tokenizeCreditCard' => {
+ 'paymentMethod' => {
+ 'id' => 'tokencc_bc_72n3ms_74wsn3_jp2vn4_gjj62v_g33',
+ 'details' => {
+ 'last4' => '1111'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => 'a093afbb-42a9-4a85-973f-0ca79dff9ba6'
+ }
+ }
+ end
+
+ def token_bank_response
+ {
+ 'data' => {
+ 'tokenizeUsBankAccount' => {
+ 'paymentMethod' => {
+ 'id' => 'tokenusbankacct_bc_zrg45z_7wz95v_nscrks_q4zpjs_5m7',
+ 'details' => {
+ 'last4' => '0125'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => '769b26d5-27e4-4602-b51d-face8b6ffdd5'
+ }
+ }
+ end
+end
From acfa39ba630da73d21c3949e65e5deeab559eebf Mon Sep 17 00:00:00 2001
From: cristian
Date: Fri, 20 Oct 2023 09:19:49 -0500
Subject: [PATCH 192/390] Rapyd: send customer object on us payment types
(#4919)
Summary:
------------------------------
Introduces a change for authorize/purchase transactions
to only include the custome_object for us payment types.
[SER-885](https://spreedly.atlassian.net/browse/SER-885)
Remote Test:
------------------------------
Finished in 138.6279 seconds.
42 tests, 118 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
97.619% passed
*Note*: The failure test, fails becase is reference
transaction related to a wallet.
Unit Tests:
------------------------------
Finished in 42.121938 seconds.
5643 tests, 78206 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
773 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 28 +++++++----
test/unit/gateways/rapyd_test.rb | 46 +++++++++++++++++++
3 files changed, 67 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 223c7f75635..d789286d153 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -33,6 +33,7 @@
* Rapyd: Add recurrence_type field [yunnydang] #4912
* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
* SumUp: Void and partial refund calls [sinourain] #4891
+* Rapyd: send customer object on us payment types [Heavyblade] #4919
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index e23653f1f65..0eca2f368db 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -178,8 +178,8 @@ def add_creditcard(post, payment, options)
add_stored_credential(post, options)
end
- def send_customer_object?(options)
- options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring'
+ def recurring?(options = {})
+ options.dig(:stored_credential, :reason_type) == 'recurring'
end
def valid_network_transaction_id?(options)
@@ -205,7 +205,7 @@ def add_tokens(post, payment, options)
customer_id, card_id = payment.split('|')
- post[:customer] = customer_id unless send_customer_object?(options)
+ post[:customer] = customer_id unless recurring?(options)
post[:payment_method] = card_id
end
@@ -248,19 +248,31 @@ def add_payment_urls(post, options, action = '')
end
def add_customer_data(post, payment, options, action = '')
- phone_number = options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
- post[:phone_number] = phone_number.gsub(/\D/, '') unless phone_number.nil?
- post[:email] = options[:email] unless send_customer_object?(options)
+ post[:phone_number] = phone_number(options) unless phone_number(options).blank?
+ post[:email] = options[:email] unless options[:email].blank? || recurring?(options)
+
return if payment.is_a?(String)
- return add_customer_id(post, options) if options[:customer_id]
+ return add_customer_id(post, options) if options[:customer_id].present?
if action == 'store'
post.merge!(customer_fields(payment, options))
else
- post[:customer] = customer_fields(payment, options) unless send_customer_object?(options)
+ post[:customer] = customer_fields(payment, options) unless recurring?(options) || non_us_payment_type?(options)
end
end
+ def phone_number(options)
+ return '' unless address = options[:billing_address]
+
+ (address[:phone] || address[:phone_number] || '').gsub(/\D/, '')
+ end
+
+ def non_us_payment_type?(options = {})
+ return false unless options[:pm_type].present?
+
+ !options.fetch(:pm_type, '').start_with?('us_')
+ end
+
def customer_fields(payment, options)
return if options[:customer_id]
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 2fc153e1bcd..fc37b182bb1 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -440,6 +440,18 @@ def test_not_send_customer_object_for_recurring_transactions
reason_type: 'recurring',
network_transaction_id: '12345'
}
+ @options[:pm_type] = 'us_debit_mastercard_card'
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['customer']
+ assert_nil request['email']
+ end
+ end
+
+ def test_request_should_not_include_customer_object_on_non_use_paymen_types
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
@@ -448,6 +460,40 @@ def test_not_send_customer_object_for_recurring_transactions
end
end
+ def test_request_should_include_customer_object_and_email_for_us_payment_types
+ @options[:pm_type] = 'us_debit_mastercard_card'
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+
+ refute_nil request['customer']
+ refute_nil request['email']
+ assert_match(/Longbob/, request['customer']['name'])
+ assert_equal 1, request['customer']['addresses'].size
+ end
+ end
+
+ def test_getting_phone_number_from_address_object
+ assert_empty @gateway.send(:phone_number, {})
+ assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone: '123' } })
+ assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone_number: '123' } })
+ assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone_number: '1-2.3' } })
+ end
+
+ def test_detect_non_us_payment_type
+ refute @gateway.send(:non_us_payment_type?)
+ refute @gateway.send(:non_us_payment_type?, { pm_type: 'us_debit_visa_card' })
+ assert @gateway.send(:non_us_payment_type?, { pm_type: 'in_amex_card' })
+ end
+
+ def test_indicates_if_transaction_is_recurring
+ refute @gateway.send(:recurring?)
+ refute @gateway.send(:recurring?, { stored_credential: { reason_type: 'unschedule' } })
+ assert @gateway.send(:recurring?, { stored_credential: { reason_type: 'recurring' } })
+ end
+
private
def pre_scrubbed
From f64a656e47c062fb98e79c2ae4bc215607814e1c Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Fri, 20 Oct 2023 11:19:27 -0500
Subject: [PATCH 193/390] SecurionPay/Shift4_v2: authorization from. (#4913)
Modift the authorization_from when is a unsuccesful transaction, to
get the value from chargeId key.
Co-authored-by: Gustavo Sanmartin
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/securion_pay.rb | 2 +-
test/remote/gateways/remote_securion_pay_test.rb | 3 +++
test/remote/gateways/remote_shift4_v2_test.rb | 8 ++++++++
test/unit/gateways/securion_pay_test.rb | 2 +-
5 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d789286d153..adf7effacd5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -34,6 +34,7 @@
* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
* SumUp: Void and partial refund calls [sinourain] #4891
* Rapyd: send customer object on us payment types [Heavyblade] #4919
+* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb
index 0489ec924dd..5699451b1eb 100644
--- a/lib/active_merchant/billing/gateways/securion_pay.rb
+++ b/lib/active_merchant/billing/gateways/securion_pay.rb
@@ -239,7 +239,7 @@ def authorization_from(action, response)
if action == 'customers' && success?(response) && response['cards'].present?
response['cards'].first['id']
else
- success?(response) ? response['id'] : response['error']['charge']
+ success?(response) ? response['id'] : (response.dig('error', 'charge') || response.dig('error', 'chargeId'))
end
end
diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb
index 6e83fa91d76..6e6e8a82494 100644
--- a/test/remote/gateways/remote_securion_pay_test.rb
+++ b/test/remote/gateways/remote_securion_pay_test.rb
@@ -107,6 +107,9 @@ def test_authorization_and_capture
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
+ assert_match CHARGE_ID_REGEX, response.authorization
+ assert_equal response.authorization, response.params['error']['chargeId']
+ assert_equal response.message, 'The card was declined.'
end
def test_failed_capture
diff --git a/test/remote/gateways/remote_shift4_v2_test.rb b/test/remote/gateways/remote_shift4_v2_test.rb
index 7d501b34dd3..357fbfadde4 100644
--- a/test/remote/gateways/remote_shift4_v2_test.rb
+++ b/test/remote/gateways/remote_shift4_v2_test.rb
@@ -77,4 +77,12 @@ def test_successful_stored_credentials_merchant_initiated
assert_equal 'merchant_initiated', response.params['type']
assert_match CHARGE_ID_REGEX, response.authorization
end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match CHARGE_ID_REGEX, response.authorization
+ assert_equal response.authorization, response.params['error']['chargeId']
+ assert_equal response.message, 'The card was declined.'
+ end
end
diff --git a/test/unit/gateways/securion_pay_test.rb b/test/unit/gateways/securion_pay_test.rb
index 5d3dc3b115b..b37509b5f66 100644
--- a/test/unit/gateways/securion_pay_test.rb
+++ b/test/unit/gateways/securion_pay_test.rb
@@ -262,7 +262,7 @@ def test_failed_authorize
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
- assert_nil response.authorization
+ assert_equal 'char_mApucpvVbCJgo7x09Je4n9gC', response.authorization
assert response.test?
end
From 89516af31c3b8c200c76cf1e6f956f12846ebbc4 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 18 Oct 2023 16:47:32 -0700
Subject: [PATCH 194/390] Rapyd: fix the recurrence_type field
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 6 +-----
test/remote/gateways/remote_rapyd_test.rb | 1 +
test/unit/gateways/rapyd_test.rb | 2 +-
4 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index adf7effacd5..195649091ca 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -35,6 +35,7 @@
* SumUp: Void and partial refund calls [sinourain] #4891
* Rapyd: send customer object on us payment types [Heavyblade] #4919
* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
+* Rapyd: Update recurrence_type field [yunnydang] #4922
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 0eca2f368db..9fb0c046eb0 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -104,7 +104,6 @@ def add_auth_purchase(post, money, payment, options)
add_3ds(post, payment, options)
add_address(post, payment, options)
add_metadata(post, options)
- add_recurrence_type(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
add_payment_urls(post, options)
@@ -160,10 +159,6 @@ def add_initiation_type(post, options)
post[:initiation_type] = initiation_type if initiation_type
end
- def add_recurrence_type(post, options)
- post[:recurrence_type] = options[:recurrence_type] if options[:recurrence_type]
- end
-
def add_creditcard(post, payment, options)
post[:payment_method] = {}
post[:payment_method][:fields] = {}
@@ -175,6 +170,7 @@ def add_creditcard(post, payment, options)
pm_fields[:expiration_year] = payment.year.to_s
pm_fields[:name] = "#{payment.first_name} #{payment.last_name}"
pm_fields[:cvv] = payment.verification_value.to_s unless valid_network_transaction_id?(options) || payment.verification_value.blank?
+ pm_fields[:recurrence_type] = options[:recurrence_type] if options[:recurrence_type]
add_stored_credential(post, options)
end
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 7451934b175..ee76076e6eb 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -114,6 +114,7 @@ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fie
end
def test_successful_purchase_with_reccurence_type
+ @options[:pm_type] = 'gb_visa_mo_card'
response = @gateway.purchase(@amount, @credit_card, @options.merge(recurrence_type: 'recurring'))
assert_success response
assert_equal 'SUCCESS', response.message
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index fc37b182bb1..8ad16b33dcf 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -153,7 +153,7 @@ def test_success_purchase_with_recurrence_type
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal request['recurrence_type'], @options[:recurrence_type]
+ assert_equal request['payment_method']['fields']['recurrence_type'], @options[:recurrence_type]
end.respond_with(successful_purchase_response)
end
From c43a1d569e56a02de323ca2ce751fa2a575759eb Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Fri, 16 Jun 2023 10:56:51 -0500
Subject: [PATCH 195/390] Element Gateway: Add all lodging fields,
documentation can be found
[here](https://developerengine.fisglobal.com/apis/express/express-xml/classes#lodging)
---
CHANGELOG | 1 +
.../billing/gateways/element.rb | 40 ++++++++--
test/remote/gateways/remote_element_test.rb | 75 +++++++++++++++----
test/unit/gateways/element_test.rb | 44 +++++++++++
4 files changed, 141 insertions(+), 19 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 195649091ca..8eacb4f0a73 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -36,6 +36,7 @@
* Rapyd: send customer object on us payment types [Heavyblade] #4919
* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
* Rapyd: Update recurrence_type field [yunnydang] #4922
+* Element: Add lodging fields [yunnydang] #4813
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb
index 0385db547d7..b685c7bab9c 100644
--- a/lib/active_merchant/billing/gateways/element.rb
+++ b/lib/active_merchant/billing/gateways/element.rb
@@ -37,6 +37,7 @@ def purchase(money, payment, options = {})
add_transaction(xml, money, options)
add_terminal(xml, options)
add_address(xml, options)
+ add_lodging(xml, options)
end
end
@@ -51,6 +52,7 @@ def authorize(money, payment, options = {})
add_transaction(xml, money, options)
add_terminal(xml, options)
add_address(xml, options)
+ add_lodging(xml, options)
end
end
@@ -222,16 +224,44 @@ def market_code(money, options)
options[:market_code] || 'Default'
end
+ def add_lodging(xml, options)
+ if lodging = options[:lodging]
+ xml.extendedParameters do
+ xml.ExtendedParameters do
+ xml.Key 'Lodging'
+ xml.Value('xsi:type' => 'Lodging') do
+ xml.LodgingAgreementNumber lodging[:agreement_number] if lodging[:agreement_number]
+ xml.LodgingCheckInDate lodging[:check_in_date] if lodging[:check_in_date]
+ xml.LodgingCheckOutDate lodging[:check_out_date] if lodging[:check_out_date]
+ xml.LodgingRoomAmount lodging[:room_amount] if lodging[:room_amount]
+ xml.LodgingRoomTax lodging[:room_tax] if lodging[:room_tax]
+ xml.LodgingNoShowIndicator lodging[:no_show_indicator] if lodging[:no_show_indicator]
+ xml.LodgingDuration lodging[:duration] if lodging[:duration]
+ xml.LodgingCustomerName lodging[:customer_name] if lodging[:customer_name]
+ xml.LodgingClientCode lodging[:client_code] if lodging[:client_code]
+ xml.LodgingExtraChargesDetail lodging[:extra_charges_detail] if lodging[:extra_charges_detail]
+ xml.LodgingExtraChargesAmounts lodging[:extra_charges_amounts] if lodging[:extra_charges_amounts]
+ xml.LodgingPrestigiousPropertyCode lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ xml.LodgingSpecialProgramCode lodging[:special_program_code] if lodging[:special_program_code]
+ xml.LodgingChargeType lodging[:charge_type] if lodging[:charge_type]
+ end
+ end
+ end
+ end
+ end
+
def add_terminal(xml, options)
xml.terminal do
xml.TerminalID options[:terminal_id] || '01'
+ xml.TerminalType options[:terminal_type] if options[:terminal_type]
xml.CardPresentCode options[:card_present_code] || 'UseDefault'
- xml.CardholderPresentCode 'UseDefault'
- xml.CardInputCode 'UseDefault'
- xml.CVVPresenceCode 'UseDefault'
- xml.TerminalCapabilityCode 'UseDefault'
- xml.TerminalEnvironmentCode 'UseDefault'
+ xml.CardholderPresentCode options[:card_holder_present_code] || 'UseDefault'
+ xml.CardInputCode options[:card_input_code] || 'UseDefault'
+ xml.CVVPresenceCode options[:cvv_presence_code] || 'UseDefault'
+ xml.TerminalCapabilityCode options[:terminal_capability_code] || 'UseDefault'
+ xml.TerminalEnvironmentCode options[:terminal_environment_code] || 'UseDefault'
xml.MotoECICode 'NonAuthenticatedSecureECommerceTransaction'
+ xml.PartialApprovedFlag options[:partial_approved_flag] if options[:partial_approved_flag]
end
end
diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb
index 649e014bb81..3a9f2dbc42f 100644
--- a/test/remote/gateways/remote_element_test.rb
+++ b/test/remote/gateways/remote_element_test.rb
@@ -8,13 +8,14 @@ def setup
@credit_card = credit_card('4000100011112224')
@check = check
@options = {
- order_id: '1',
- billing_address: address,
- description: 'Store Purchase'
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true'
}
@google_pay_network_token = network_tokenization_credit_card(
- '4444333322221111',
+ '4000100011112224',
month: '01',
year: Time.new.year + 2,
first_name: 'Jane',
@@ -44,12 +45,12 @@ def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
- assert_match %r{Street address and postal code do not match}, response.avs_result['message']
+ assert_match %r{Street address and 5-digit postal code match.}, response.avs_result['message']
assert_match %r{CVV matches}, response.cvv_result['message']
end
def test_failed_purchase
- @amount = 20
+ @amount = 51
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Declined', response.message
@@ -119,19 +120,65 @@ def test_successful_purchase_with_duplicate_check_disable_flag
end
def test_successful_purchase_with_duplicate_override_flag
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: true))
- assert_success response
- assert_equal 'Approved', response.message
+ options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase'
+ }
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: false))
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: true))
assert_success response
assert_equal 'Approved', response.message
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_overrride_flag: 'true'))
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: 'true'))
assert_success response
assert_equal 'Approved', response.message
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx'))
+ # Due to the way these new creds are configured, they fail on duplicate transactions.
+ # We expect failures if duplicate_override_flag: false
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_lodging_and_all_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'Sale',
+ charge_type: 'Restaurant'
+ },
+ card_holder_present_code: 'ECommerce',
+ card_input_code: 'ManualKeyed',
+ card_present_code: 'NotPresent',
+ cvv_presence_code: 'NotProvided',
+ market_code: 'HotelLodging',
+ terminal_capability_code: 'KeyEntered',
+ terminal_environment_code: 'ECommerce',
+ terminal_type: 'ECommerce',
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
assert_success response
assert_equal 'Approved', response.message
end
@@ -166,11 +213,11 @@ def test_successful_authorize_and_capture
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
- assert_equal 'Success', capture.message
+ assert_equal 'Approved', capture.message
end
def test_failed_authorize
- @amount = 20
+ @amount = 51
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Declined', response.message
diff --git a/test/unit/gateways/element_test.rb b/test/unit/gateways/element_test.rb
index bf461d6704a..694af43d9a9 100644
--- a/test/unit/gateways/element_test.rb
+++ b/test/unit/gateways/element_test.rb
@@ -166,6 +166,50 @@ def test_successful_purchase_with_card_present_code
assert_success response
end
+ def test_successful_purchase_with_lodging_and_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: 182726718192,
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'Sale',
+ charge_type: 'Restaurant'
+ }
+ }
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, lodging_options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '182726718192', data
+ assert_match '20250910', data
+ assert_match '20250915', data
+ assert_match '1000', data
+ assert_match '0', data
+ assert_match '0', data
+ assert_match '5', data
+ assert_match 'francois dubois', data
+ assert_match 'Default', data
+ assert_match '01', data
+ assert_match 'Default', data
+ assert_match 'DollarLimit500', data
+ assert_match 'Sale', data
+ assert_match 'Restaurant', data
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
def test_successful_purchase_with_payment_type
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed'))
From 791eae3d113b76e97b32520827a1d9d2ec3c4261 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 16 Oct 2023 16:09:33 -0700
Subject: [PATCH 196/390] SafeCharge (Nuvei): Fix the credit method for
sg_CreditType field
---
CHANGELOG | 1 +
.../billing/gateways/safe_charge.rb | 2 +-
.../gateways/remote_safe_charge_test.rb | 10 ++++++++++
test/unit/gateways/safe_charge_test.rb | 20 +++++++++++++++++++
4 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 8eacb4f0a73..5ca808e735e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -37,6 +37,7 @@
* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
* Rapyd: Update recurrence_type field [yunnydang] #4922
* Element: Add lodging fields [yunnydang] #4813
+* SafeCharge: Update sg_CreditType field on the credit method [yunnydang] #4918
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index 42ed13f3ead..a4be0c6fcd1 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -88,7 +88,7 @@ def credit(money, payment, options = {})
add_transaction_data('Credit', post, money, options)
add_customer_details(post, payment, options)
- post[:sg_CreditType] = 1
+ options[:unreferenced_refund].to_s == 'true' ? post[:sg_CreditType] = 2 : post[:sg_CreditType] = 1
commit(post)
end
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index ee1a0295e09..8e8fe4dd945 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -268,6 +268,16 @@ def test_successful_unreferenced_refund
assert_equal 'Success', refund.message
end
+ def test_successful_unreferenced_refund_with_credit
+ option = {
+ unreferenced_refund: true
+ }
+
+ assert general_credit = @gateway.credit(@amount, @credit_card, option)
+ assert_success general_credit
+ assert_equal 'Success', general_credit.message
+ end
+
def test_successful_credit
response = @gateway.credit(@amount, credit_card('4444436501403986'), @options)
assert_success response
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index 796ee649c8a..23a579e569f 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -223,6 +223,26 @@ def test_successful_refund_without_unreferenced_refund
assert_success refund
end
+ def test_successful_credit_with_unreferenced_refund
+ credit = stub_comms do
+ @gateway.credit(@amount, @credit_card, @options.merge(unreferenced_refund: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal(data.split('&').include?('sg_CreditType=2'), true)
+ end.respond_with(successful_credit_response)
+
+ assert_success credit
+ end
+
+ def test_successful_credit_without_unreferenced_refund
+ credit = stub_comms do
+ @gateway.credit(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal(data.split('&').include?('sg_CreditType=1'), true)
+ end.respond_with(successful_credit_response)
+
+ assert_success credit
+ end
+
def test_failed_refund
@gateway.expects(:ssl_post).returns(failed_refund_response)
From 4690f860ce86d992987896302ba3d319b0eec3b0 Mon Sep 17 00:00:00 2001
From: cristian
Date: Mon, 23 Oct 2023 09:03:55 -0500
Subject: [PATCH 197/390] Rapyd: add force_3ds_secure flag (#4927)
Summary:
------------------------------
Introduces a change for 3ds transactions when dealing
with 3DS gateway specific besides the standard fields
a GSF needs to be sent in order to force the 3DS flow,
this change is needed because the '3d_require' attribute
works as force flag not like feature flag.
[SER-889](https://spreedly.atlassian.net/browse/SER-889)
Remote Test:
------------------------------
Finished in 115.583277 seconds.
42 tests, 118 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
97.619% passed
*Note*: The failure test, fails becase is reference
transaction related to a wallet.
Unit Tests:
------------------------------
Finished in 42.100949 seconds.
5643 tests, 78206 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
773 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 2 +-
test/unit/gateways/rapyd_test.rb | 14 +++++++++++++-
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5ca808e735e..814f7d93d78 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -38,6 +38,7 @@
* Rapyd: Update recurrence_type field [yunnydang] #4922
* Element: Add lodging fields [yunnydang] #4813
* SafeCharge: Update sg_CreditType field on the credit method [yunnydang] #4918
+* Rapyd: add force_3ds_secure flag [Heavyblade] #4927
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 9fb0c046eb0..6c3fa142e43 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -207,7 +207,7 @@ def add_tokens(post, payment, options)
def add_3ds(post, payment, options)
if options[:execute_threed] == true
- post[:payment_method_options] = { '3d_required' => true }
+ post[:payment_method_options] = { '3d_required' => true } if options[:force_3d_secure].present?
elsif three_d_secure = options[:three_d_secure]
post[:payment_method_options] = {}
post[:payment_method_options]['3d_required'] = three_d_secure[:required]
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 8ad16b33dcf..e164a75b2dc 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -157,7 +157,7 @@ def test_success_purchase_with_recurrence_type
end.respond_with(successful_purchase_response)
end
- def test_successful_purchase_with_3ds_gateway_specific
+ def test_successful_purchase_with_3ds_global
@options[:three_d_secure] = {
required: true,
version: '2.1.0'
@@ -173,6 +173,18 @@ def test_successful_purchase_with_3ds_gateway_specific
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_3ds_gateway_specific
+ @options.merge!(execute_threed: true, force_3d_secure: true)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['payment_method_options']['3d_required'], true
+ assert_nil request['payment_method_options']['3d_version']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_request).returns(failed_purchase_response)
From b1ea7b42498fa0c5a53c9d12f8fa630054a3be77 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Thu, 19 Oct 2023 17:15:32 -0400
Subject: [PATCH 198/390] Beanstream: add alternate option for passing phone
number
---
CHANGELOG | 1 +
.../billing/gateways/beanstream/beanstream_core.rb | 2 +-
test/unit/gateways/beanstream_test.rb | 13 +++++++++++++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 814f7d93d78..3026458d831 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -39,6 +39,7 @@
* Element: Add lodging fields [yunnydang] #4813
* SafeCharge: Update sg_CreditType field on the credit method [yunnydang] #4918
* Rapyd: add force_3ds_secure flag [Heavyblade] #4927
+* Beanstream: add alternate option for passing phone number [jcreiff] #4923
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
index 2239c87a75d..87e5c89ba5e 100644
--- a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
+++ b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
@@ -228,7 +228,7 @@ def add_address(post, options)
if billing_address = options[:billing_address] || options[:address]
post[:ordName] = billing_address[:name]
- post[:ordPhoneNumber] = billing_address[:phone]
+ post[:ordPhoneNumber] = billing_address[:phone] || billing_address[:phone_number]
post[:ordAddress1] = billing_address[:address1]
post[:ordAddress2] = billing_address[:address2]
post[:ordCity] = billing_address[:city]
diff --git a/test/unit/gateways/beanstream_test.rb b/test/unit/gateways/beanstream_test.rb
index 886a4479e1c..fa9f748c9ff 100644
--- a/test/unit/gateways/beanstream_test.rb
+++ b/test/unit/gateways/beanstream_test.rb
@@ -302,6 +302,19 @@ def test_sends_email_without_addresses
assert_success response
end
+ def test_sends_alternate_phone_number_value
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '9191234567'
+
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/ordPhoneNumber=9191234567/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_transcript_scrubbing
assert_equal scrubbed_transcript, @gateway.scrub(transcript)
end
From 544806e1a829ba172cb5cb02ea5cbf2c7f1dedd9 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 11 Aug 2023 14:42:22 -0500
Subject: [PATCH 199/390] Authorize.NET: Update network token method
Update network token payment method to clarify what is being
sent.
Remote:
85 tests, 304 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
122 tests, 684 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/authorize_net.rb | 61 +++++++++----------
.../gateways/remote_authorize_net_test.rb | 16 ++++-
test/unit/gateways/authorize_net_test.rb | 32 +++++-----
4 files changed, 59 insertions(+), 51 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3026458d831..ca01e68a50d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -40,6 +40,7 @@
* SafeCharge: Update sg_CreditType field on the credit method [yunnydang] #4918
* Rapyd: add force_3ds_secure flag [Heavyblade] #4927
* Beanstream: add alternate option for passing phone number [jcreiff] #4923
+* AuthorizeNet: Update network token method [almalee24] #4852
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb
index 2016756aaac..f83ac599e38 100644
--- a/lib/active_merchant/billing/gateways/authorize_net.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net.rb
@@ -89,7 +89,6 @@ class AuthorizeNetGateway < Gateway
2 => /\A;(?[\d]{1,19}+)=(?[\d]{0,4}|=)(?[\d]{0,3}|=)(?.*)\?\Z/
}.freeze
- APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT'
PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155'
INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54'
@@ -165,7 +164,7 @@ def credit(amount, payment, options = {})
xml.transactionType('refundTransaction')
xml.amount(amount(amount))
- add_payment_source(xml, payment, options, :credit)
+ add_payment_method(xml, payment, options, :credit)
xml.refTransId(transaction_id_from(options[:transaction_id])) if options[:transaction_id]
add_invoice(xml, 'refundTransaction', options)
add_customer_data(xml, payment, options)
@@ -262,7 +261,7 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options)
xml.transactionRequest do
xml.transactionType(transaction_type)
xml.amount(amount(amount))
- add_payment_source(xml, payment, options)
+ add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
add_tax_fields(xml, options)
add_duty_fields(xml, options)
@@ -287,7 +286,7 @@ def add_cim_auth_purchase(xml, transaction_type, amount, payment, options)
add_tax_fields(xml, options)
add_shipping_fields(xml, options)
add_duty_fields(xml, options)
- add_payment_source(xml, payment, options)
+ add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
add_tax_exempt_status(xml, options)
end
@@ -407,20 +406,27 @@ def normal_void(authorization, options)
end
end
- def add_payment_source(xml, source, options, action = nil)
- return unless source
+ def add_payment_method(xml, payment_method, options, action = nil)
+ return unless payment_method
- if source.is_a?(String)
- add_token_payment_method(xml, source, options)
- elsif card_brand(source) == 'check'
- add_check(xml, source)
- elsif card_brand(source) == 'apple_pay'
- add_apple_pay_payment_token(xml, source)
+ case payment_method
+ when String
+ add_token_payment_method(xml, payment_method, options)
+ when Check
+ add_check(xml, payment_method)
else
- add_credit_card(xml, source, action)
+ if network_token?(payment_method, options, action)
+ add_network_token(xml, payment_method)
+ else
+ add_credit_card(xml, payment_method, action)
+ end
end
end
+ def network_token?(payment_method, options, action)
+ payment_method.class == NetworkTokenizationCreditCard && action != :credit && options[:turn_on_nt_flow]
+ end
+
def camel_case_lower(key)
String(key).split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
end
@@ -526,17 +532,20 @@ def add_token_payment_method(xml, token, options)
xml.customerPaymentProfileId(customer_payment_profile_id)
end
- def add_apple_pay_payment_token(xml, apple_pay_payment_token)
+ def add_network_token(xml, payment_method)
xml.payment do
- xml.opaqueData do
- xml.dataDescriptor(APPLE_PAY_DATA_DESCRIPTOR)
- xml.dataValue(Base64.strict_encode64(apple_pay_payment_token.payment_data.to_json))
+ xml.creditCard do
+ xml.cardNumber(truncate(payment_method.number, 16))
+ xml.expirationDate(format(payment_method.month, :two_digits) + '/' + format(payment_method.year, :four_digits))
+ xml.isPaymentToken(true)
+ xml.cryptogram(payment_method.payment_cryptogram)
end
end
end
def add_market_type_device_type(xml, payment, options)
- return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay'
+ return unless payment.is_a?(CreditCard)
+ return if payment.is_a?(NetworkTokenizationCreditCard)
if valid_track_data
xml.retail do
@@ -754,13 +763,7 @@ def create_customer_payment_profile(credit_card, options)
xml.customerProfileId options[:customer_profile_id]
xml.paymentProfile do
add_billing_address(xml, credit_card, options)
- xml.payment do
- xml.creditCard do
- xml.cardNumber(truncate(credit_card.number, 16))
- xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
- xml.cardCode(credit_card.verification_value) if credit_card.verification_value
- end
- end
+ add_credit_card(xml, credit_card, :cim_store_update)
end
end
end
@@ -776,13 +779,7 @@ def create_customer_profile(credit_card, options)
xml.customerType('individual')
add_billing_address(xml, credit_card, options)
add_shipping_address(xml, options, 'shipToList')
- xml.payment do
- xml.creditCard do
- xml.cardNumber(truncate(credit_card.number, 16))
- xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
- xml.cardCode(credit_card.verification_value) if credit_card.verification_value
- end
- end
+ add_credit_card(xml, credit_card, :cim_store)
end
end
end
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index e6388238550..3670f8e9254 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -69,7 +69,7 @@ def test_successful_purchase
def test_successful_purchase_with_google_pay
@payment_token.source = :google_pay
- response = @gateway.purchase(@amount, @payment_token, @options)
+ response = @gateway.purchase(@amount, @payment_token, @options.merge(turn_on_nt_flow: true))
assert_success response
assert response.test?
@@ -78,6 +78,16 @@ def test_successful_purchase_with_google_pay
end
def test_successful_purchase_with_apple_pay
+ @payment_token.source = :apple_pay
+ response = @gateway.purchase(@amount, @payment_token, @options.merge(turn_on_nt_flow: true))
+
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_apple_pay_without_turn_on_nt_flow_field
@payment_token.source = :apple_pay
response = @gateway.purchase(@amount, @payment_token, @options)
@@ -903,8 +913,8 @@ def test_successful_refund_with_network_tokenization
def test_successful_credit_with_network_tokenization
credit_card = network_tokenization_credit_card(
- '4000100011112224',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ '5424000000000015',
+ payment_cryptogram: 'EjRWeJASNFZ4kBI0VniQEjRWeJA=',
verification_value: nil
)
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index b0f3b957b0e..deaa457f8ce 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -16,11 +16,15 @@ def setup
@amount = 100
@credit_card = credit_card
@check = check
- @apple_pay_payment_token = ActiveMerchant::Billing::ApplePayPaymentToken.new(
- { data: 'encoded_payment_data' },
- payment_instrument_name: 'SomeBank Visa',
- payment_network: 'Visa',
- transaction_identifier: 'transaction123'
+ @payment_token = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
+ brand: 'visa',
+ eci: '05',
+ month: '09',
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
)
@options = {
@@ -153,7 +157,7 @@ def test_device_type_used_from_options_if_included_with_valid_track_data
end
def test_market_type_not_included_for_apple_pay_or_echeck
- [@check, @apple_pay_payment_token].each do |payment|
+ [@check, @payment_token].each do |payment|
stub_comms do
@gateway.purchase(@amount, payment)
end.check_request do |_endpoint, data, _headers|
@@ -265,12 +269,10 @@ def test_failed_echeck_authorization
def test_successful_apple_pay_authorization
response = stub_comms do
- @gateway.authorize(@amount, @apple_pay_payment_token)
+ @gateway.authorize(@amount, @payment_token)
end.check_request do |_endpoint, data, _headers|
- parse(data) do |doc|
- assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content
- assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content
- end
+ assert_no_match(/true<\/isPaymentToken>/, data)
+ assert_match(//, data)
end.respond_with(successful_authorize_response)
assert response
@@ -281,12 +283,10 @@ def test_successful_apple_pay_authorization
def test_successful_apple_pay_purchase
response = stub_comms do
- @gateway.purchase(@amount, @apple_pay_payment_token)
+ @gateway.purchase(@amount, @payment_token, { turn_on_nt_flow: true })
end.check_request do |_endpoint, data, _headers|
- parse(data) do |doc|
- assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content
- assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content
- end
+ assert_match(/true<\/isPaymentToken>/, data)
+ assert_no_match(//, data)
end.respond_with(successful_purchase_response)
assert response
From 2146f9478dc385c11c86c2b61e6aefd67ede1b62 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 3 Oct 2023 13:13:23 -0500
Subject: [PATCH 200/390] Adding Oauth Response for access tokens
Add new OAuth Response error handling to PayTrace,
Quickbooks, Simetrik, Alelo, CheckoutV2 and Airwallex.
---
CHANGELOG | 1 +
.../billing/gateways/airwallex.rb | 18 ++++++++++---
lib/active_merchant/billing/gateways/alelo.rb | 26 ++++++++++++++++---
.../billing/gateways/checkout_v2.rb | 19 +++++++++++---
.../billing/gateways/pay_trace.rb | 21 ++++++++++-----
.../billing/gateways/quickbooks.rb | 19 ++++++++++----
.../billing/gateways/simetrik.rb | 18 ++++++++-----
lib/active_merchant/errors.rb | 12 +++++++++
test/remote/gateways/remote_airwallex_test.rb | 7 +++++
test/remote/gateways/remote_alelo_test.rb | 10 ++++---
.../gateways/remote_checkout_v2_test.rb | 16 ++++++++++++
test/remote/gateways/remote_pay_trace_test.rb | 17 ++++++++++++
.../remote/gateways/remote_quickbooks_test.rb | 17 ++++++++++++
test/remote/gateways/remote_simetrik_test.rb | 16 ++++++++++++
test/unit/gateways/airwallex_test.rb | 21 +++++++--------
test/unit/gateways/alelo_test.rb | 9 +++++++
test/unit/gateways/checkout_v2_test.rb | 21 +++++++--------
test/unit/gateways/pay_trace_test.rb | 25 ++++++++++--------
test/unit/gateways/quickbooks_test.rb | 9 +++++++
test/unit/gateways/simetrik_test.rb | 22 ++++++++--------
20 files changed, 248 insertions(+), 76 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index ca01e68a50d..01fac58e2a9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -41,6 +41,7 @@
* Rapyd: add force_3ds_secure flag [Heavyblade] #4927
* Beanstream: add alternate option for passing phone number [jcreiff] #4923
* AuthorizeNet: Update network token method [almalee24] #4852
+* Adding Oauth Response for access tokens [almalee24] #4907
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index fd1e06f427d..d2a20c2cc1a 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = setup_access_token
+ @access_token = options[:access_token] || setup_access_token
end
def purchase(money, card, options = {})
@@ -133,8 +133,20 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- response = ssl_post(build_request_url(:login), nil, token_headers)
- JSON.parse(response)['token']
+
+ begin
+ raw_response = ssl_post(build_request_url(:login), nil, token_headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = JSON.parse(raw_response)
+ if (token = response['token'])
+ token
+ else
+ oauth_response = Response.new(false, response['message'])
+ raise OAuthResponseError.new(oauth_response)
+ end
+ end
end
def build_request_url(action, id = nil)
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index fd1cfc11a5f..381b5859372 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,8 +110,18 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
- Response.new(true, parsed[:access_token], parsed)
+ begin
+ raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ if (access_token = response[:access_token])
+ Response.new(true, access_token, response)
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def remote_encryption_key(access_token)
@@ -144,9 +154,11 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
+ raise e unless retry?(try_again, e, :access_token)
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -206,9 +218,11 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
+ if retry?(try_again, e, :encryption_key)
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -217,6 +231,10 @@ def commit(action, body, options, try_again = true)
end
end
+ def retry?(try_again, error, key)
+ try_again && %w(401 404).include?(error.response.code) && @options[key].present?
+ end
+
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index d0dddaff429..bed352e9a3b 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -18,13 +18,13 @@ class CheckoutV2Gateway < Gateway
def initialize(options = {})
@options = options
- @access_token = nil
+ @access_token = options[:access_token] || nil
if options.has_key?(:secret_key)
requires!(options, :secret_key)
else
requires!(options, :client_id, :client_secret)
- @access_token = setup_access_token
+ @access_token ||= setup_access_token
end
super
@@ -428,8 +428,19 @@ def access_token_url
def setup_access_token
request = 'grant_type=client_credentials'
- response = parse(ssl_post(access_token_url, request, access_token_header))
- response['access_token']
+ begin
+ raw_response = ssl_post(access_token_url, request, access_token_header)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+
+ if (access_token = response['access_token'])
+ access_token
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def commit(action, post, options, authorization = nil, method = :post)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 53203d51f96..8c338687df1 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token
+ acquire_access_token unless options[:access_token]
end
def purchase(money, payment_or_customer_id, options = {})
@@ -187,10 +187,15 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = JSON.parse(response)
+ json_response = parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
+ if json_response.include?('error')
+ oauth_response = Response.new(false, json_response['error_description'])
+ raise OAuthResponseError.new(oauth_response)
+ else
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
+ end
end
private
@@ -373,6 +378,12 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
+ handle_final_response(action, response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -385,8 +396,6 @@ def commit(action, parameters)
test: test?,
error_code: success ? nil : error_code_from(response)
)
- rescue JSON::ParserError
- unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6d9f14f3445..6197581bbe7 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,12 +305,17 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- response = ssl_post(REFRESH_URI, data, headers)
- json_response = JSON.parse(response)
+ begin
+ response = ssl_post(REFRESH_URI, data, headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
+ end
end
def cvv_code_from(response)
@@ -358,6 +363,10 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
+
+ error_code = JSON.parse(response_error.response.body)['code']
+ raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
+
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index 5c436acab95..f3b0863eef8 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = {}
+ @access_token = options[:access_token] || {}
sign_access_token()
end
@@ -356,12 +356,18 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
- response = parse(ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- }))
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ begin
+ raw_response = ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ })
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ end
end
end
end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index b017c45114a..5f68f0e59b5 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,6 +23,18 @@ def initialize(response, message = nil)
end
def to_s
+ if response.kind_of?(String)
+ if response.start_with?('Failed')
+ return response
+ else
+ return "Failed with #{response}"
+ end
+ end
+
+ if response.respond_to?(:message)
+ return response.message if response.message.start_with?('Failed')
+ end
+
"Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 5cbc4053c7d..24aa9fe3b61 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,6 +14,13 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
+ gateway.send :setup_access_token
+ end
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index be4d9ae9059..8a4fef24e7b 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::ResponseError) do
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,9 +145,11 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match %r{invalid_client}, response.message
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_match(/401/, error.message)
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 7361eecea9d..13149453ae4 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -160,6 +160,22 @@ def setup
)
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.send :setup_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 400 Bad Request'
+ end
+
def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index b10e5119e3a..6c56f840353 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -39,6 +39,23 @@ def test_acquire_token
assert_not_nil response['access_token']
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ gateway.send :acquire_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(1000, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ end
+
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index f3457706af5..6b295967b22 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,6 +18,23 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+ gateway.send :refresh_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_access_token
+ gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
+
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index b1e2eb24daf..90f66e2f844 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,6 +78,22 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.send :fetch_access_token
+ end
+ end
+
+ def test_failed_authorize_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.authorize(@amount, @credit_card, @authorize_options_success)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index ade541ce88e..6ad38180082 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class AirwallexGateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -28,6 +18,15 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_setup_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
+ end
+
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 3e6c9f0c1c9..86e35e917f3 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,6 +19,15 @@ def setup
}
end
+ def test_fetch_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway .send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 309b32587e0..f6c33802138 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CheckoutV2Gateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -17,7 +7,7 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
@gateway_api = CheckoutV2Gateway.new({
secret_key: '1111111111111',
public_key: '2222222222222'
@@ -27,6 +17,15 @@ def setup
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with 400 Bad Request/, error.message)
+ end
+
def test_successful_purchase
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 09f13807e83..42be462bbf0 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class PayTraceGateway < Gateway
- def acquire_access_token
- @options[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -24,6 +14,19 @@ def setup
}
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ access_token_response = {
+ error: 'invalid_grant',
+ error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ }.to_json
+ @gateway.expects(:ssl_post).returns(access_token_response)
+ @gateway.send(:acquire_access_token)
+ end
+
+ assert_match(/Failed with The provided authorization grant is invalid/, error.message)
+ end
+
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 7e48cce44ef..9b7a6f94a5b 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,6 +32,15 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
+ def test_refresh_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @oauth_2_gateway .send(:refresh_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb
index c120b2e99fe..f47a31203a9 100644
--- a/test/unit/gateways/simetrik_test.rb
+++ b/test/unit/gateways/simetrik_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SimetrikGateway < Gateway
- def fetch_access_token
- @access_token[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class SimetrikTest < Test::Unit::TestCase
def setup
@token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205'
@@ -17,7 +7,8 @@ def setup
@gateway = SimetrikGateway.new(
client_id: 'client_id',
client_secret: 'client_secret_key',
- audience: 'audience_url'
+ audience: 'audience_url',
+ access_token: { expires_at: Time.new.to_i }
)
@credit_card = CreditCard.new(
first_name: 'sergiod',
@@ -170,6 +161,15 @@ def test_success_purchase_with_billing_address
assert response.test?
end
+ def test_fetch_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway.send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_success_purchase_with_shipping_address
expected_body = JSON.parse(@authorize_capture_expected_body.dup)
expected_body['forward_payload']['order']['shipping_address'] = address
From ba4a1e3d3fd014907eed04c93656a6f8c2ac7f39 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 16 Oct 2023 11:36:12 -0500
Subject: [PATCH 201/390] GlobalCollect: Add support for 3DS exemptions
Remote
53 tests, 121 assertions, 10 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications
79.2453% passed
Unit
46 tests, 234 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/global_collect.rb | 7 +++++++
test/remote/gateways/remote_global_collect_test.rb | 7 +++++++
test/unit/gateways/global_collect_test.rb | 10 ++++++++++
4 files changed, 25 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 01fac58e2a9..16a1ec16710 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -42,6 +42,7 @@
* Beanstream: add alternate option for passing phone number [jcreiff] #4923
* AuthorizeNet: Update network token method [almalee24] #4852
* Adding Oauth Response for access tokens [almalee24] #4907
+* GlobalCollect: Added support for 3DS exemption request field [almalee24] #4917
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index 52b5409193e..240f7179e1d 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -40,6 +40,7 @@ def authorize(money, payment, options = {})
add_creator_info(post, options)
add_fraud_fields(post, options)
add_external_cardholder_authentication_data(post, options)
+ add_threeds_exemption_data(post, options)
commit(:post, :authorize, post, options: options)
end
@@ -406,6 +407,12 @@ def add_external_cardholder_authentication_data(post, options)
post['cardPaymentMethodSpecificInput']['threeDSecure']['externalCardholderAuthenticationData'] = authentication_data unless authentication_data.empty?
end
+ def add_threeds_exemption_data(post, options)
+ return unless options[:three_ds_exemption_type]
+
+ post['cardPaymentMethodSpecificInput']['transactionChannel'] = 'MOTO' if options[:three_ds_exemption_type] == 'moto'
+ end
+
def add_number_of_installments(post, options)
post['order']['additionalInput']['numberOfInstallments'] = options[:number_of_installments] if options[:number_of_installments]
end
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index cd8efed3c02..8fa0b9c091d 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -196,6 +196,13 @@ def test_successful_purchase_with_requires_approval_false
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
+ def test_successful_authorize_with_moto_exemption
+ options = @options.merge(three_ds_exemption_type: 'moto')
+
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ end
+
def test_successful_authorize_via_normalized_3ds2_fields
options = @options.merge(
three_d_secure: {
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index 537d233f5cc..e4c96bc3e8a 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -433,6 +433,16 @@ def test_does_not_send_3ds_auth_when_empty
assert_success response
end
+ def test_successful_authorize_with_3ds_exemption
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@accepted_amount, @credit_card, { three_ds_exemption_type: 'moto' })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"transactionChannel\":\"MOTO\"/, data)
+ end.respond_with(successful_authorize_with_3ds2_data_response)
+
+ assert_success response
+ end
+
def test_truncates_first_name_to_15_chars
credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname' })
From 7384f2244e2184b56563a3e10cab762558346f15 Mon Sep 17 00:00:00 2001
From: Nick Ashton
Date: Wed, 25 Oct 2023 10:31:22 -0400
Subject: [PATCH 202/390] Revert "Rapyd: send customer object on us payment
types (#4919)" (#4930)
This reverts commit acfa39ba630da73d21c3949e65e5deeab559eebf.
---
CHANGELOG | 1 -
lib/active_merchant/billing/gateways/rapyd.rb | 28 ++++-------
test/unit/gateways/rapyd_test.rb | 46 -------------------
3 files changed, 8 insertions(+), 67 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 16a1ec16710..3a556e3f0ff 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -33,7 +33,6 @@
* Rapyd: Add recurrence_type field [yunnydang] #4912
* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
* SumUp: Void and partial refund calls [sinourain] #4891
-* Rapyd: send customer object on us payment types [Heavyblade] #4919
* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
* Rapyd: Update recurrence_type field [yunnydang] #4922
* Element: Add lodging fields [yunnydang] #4813
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 6c3fa142e43..f3936b7a33e 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -174,8 +174,8 @@ def add_creditcard(post, payment, options)
add_stored_credential(post, options)
end
- def recurring?(options = {})
- options.dig(:stored_credential, :reason_type) == 'recurring'
+ def send_customer_object?(options)
+ options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring'
end
def valid_network_transaction_id?(options)
@@ -201,7 +201,7 @@ def add_tokens(post, payment, options)
customer_id, card_id = payment.split('|')
- post[:customer] = customer_id unless recurring?(options)
+ post[:customer] = customer_id unless send_customer_object?(options)
post[:payment_method] = card_id
end
@@ -244,31 +244,19 @@ def add_payment_urls(post, options, action = '')
end
def add_customer_data(post, payment, options, action = '')
- post[:phone_number] = phone_number(options) unless phone_number(options).blank?
- post[:email] = options[:email] unless options[:email].blank? || recurring?(options)
-
+ phone_number = options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ post[:phone_number] = phone_number.gsub(/\D/, '') unless phone_number.nil?
+ post[:email] = options[:email] unless send_customer_object?(options)
return if payment.is_a?(String)
- return add_customer_id(post, options) if options[:customer_id].present?
+ return add_customer_id(post, options) if options[:customer_id]
if action == 'store'
post.merge!(customer_fields(payment, options))
else
- post[:customer] = customer_fields(payment, options) unless recurring?(options) || non_us_payment_type?(options)
+ post[:customer] = customer_fields(payment, options) unless send_customer_object?(options)
end
end
- def phone_number(options)
- return '' unless address = options[:billing_address]
-
- (address[:phone] || address[:phone_number] || '').gsub(/\D/, '')
- end
-
- def non_us_payment_type?(options = {})
- return false unless options[:pm_type].present?
-
- !options.fetch(:pm_type, '').start_with?('us_')
- end
-
def customer_fields(payment, options)
return if options[:customer_id]
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index e164a75b2dc..efebb6976e0 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -452,18 +452,6 @@ def test_not_send_customer_object_for_recurring_transactions
reason_type: 'recurring',
network_transaction_id: '12345'
}
- @options[:pm_type] = 'us_debit_mastercard_card'
-
- stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@amount, @credit_card, @options)
- end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
- request = JSON.parse(data)
- assert_nil request['customer']
- assert_nil request['email']
- end
- end
-
- def test_request_should_not_include_customer_object_on_non_use_paymen_types
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
@@ -472,40 +460,6 @@ def test_request_should_not_include_customer_object_on_non_use_paymen_types
end
end
- def test_request_should_include_customer_object_and_email_for_us_payment_types
- @options[:pm_type] = 'us_debit_mastercard_card'
-
- stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@amount, @credit_card, @options)
- end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
- request = JSON.parse(data)
-
- refute_nil request['customer']
- refute_nil request['email']
- assert_match(/Longbob/, request['customer']['name'])
- assert_equal 1, request['customer']['addresses'].size
- end
- end
-
- def test_getting_phone_number_from_address_object
- assert_empty @gateway.send(:phone_number, {})
- assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone: '123' } })
- assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone_number: '123' } })
- assert_equal '123', @gateway.send(:phone_number, { billing_address: { phone_number: '1-2.3' } })
- end
-
- def test_detect_non_us_payment_type
- refute @gateway.send(:non_us_payment_type?)
- refute @gateway.send(:non_us_payment_type?, { pm_type: 'us_debit_visa_card' })
- assert @gateway.send(:non_us_payment_type?, { pm_type: 'in_amex_card' })
- end
-
- def test_indicates_if_transaction_is_recurring
- refute @gateway.send(:recurring?)
- refute @gateway.send(:recurring?, { stored_credential: { reason_type: 'unschedule' } })
- assert @gateway.send(:recurring?, { stored_credential: { reason_type: 'recurring' } })
- end
-
private
def pre_scrubbed
From 8a9140ba5b9daaa3e8a7b37ff2994263c28de1c1 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Thu, 26 Oct 2023 10:27:24 -0400
Subject: [PATCH 203/390] NMI: Update supported countries list
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/nmi.rb | 2 +-
test/unit/gateways/nmi_test.rb | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3a556e3f0ff..5ade3188784 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -42,6 +42,7 @@
* AuthorizeNet: Update network token method [almalee24] #4852
* Adding Oauth Response for access tokens [almalee24] #4907
* GlobalCollect: Added support for 3DS exemption request field [almalee24] #4917
+* NMI: Update supported countries list [jcreiff] #4931
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb
index aee8fa754a7..de09204b839 100644
--- a/lib/active_merchant/billing/gateways/nmi.rb
+++ b/lib/active_merchant/billing/gateways/nmi.rb
@@ -8,7 +8,7 @@ class NmiGateway < Gateway
self.test_url = self.live_url = 'https://secure.networkmerchants.com/api/transact.php'
self.default_currency = 'USD'
self.money_format = :dollars
- self.supported_countries = ['US']
+ self.supported_countries = %w[US CA]
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'http://nmi.com/'
self.display_name = 'NMI'
diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb
index 3736b25b6bb..badce95ff5c 100644
--- a/test/unit/gateways/nmi_test.rb
+++ b/test/unit/gateways/nmi_test.rb
@@ -588,7 +588,7 @@ def test_blank_cvv_not_sent
end
def test_supported_countries
- assert_equal 1, (['US'] | NmiGateway.supported_countries).size
+ assert_equal 2, (%w[US CA] | NmiGateway.supported_countries).size
end
def test_supported_card_types
From 7cff0c0d2d5d12dd307b20a2be6668af999597d9 Mon Sep 17 00:00:00 2001
From: Joe Reiff
Date: Fri, 20 Oct 2023 16:10:29 -0400
Subject: [PATCH 204/390] Adyen: Add mcc field
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/adyen.rb | 1 +
test/remote/gateways/remote_adyen_test.rb | 14 ++++++++++++++
test/unit/gateways/adyen_test.rb | 3 ++-
4 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5ade3188784..5d1033d0e6a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -43,6 +43,7 @@
* Adding Oauth Response for access tokens [almalee24] #4907
* GlobalCollect: Added support for 3DS exemption request field [almalee24] #4917
* NMI: Update supported countries list [jcreiff] #4931
+* Adyen: Add mcc field [jcreiff] #4926
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index 43cd1e9029e..11a003f1f9f 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -255,6 +255,7 @@ def add_extra_data(post, payment, options)
post[:shopperIP] = options[:shopper_ip] || options[:ip] if options[:shopper_ip] || options[:ip]
post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
post[:store] = options[:store] if options[:store]
+ post[:mcc] = options[:mcc] if options[:mcc]
add_shopper_data(post, payment, options)
add_additional_data(post, payment, options)
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index dfa28c6ded1..1c49c85eee9 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -1485,6 +1485,20 @@ def test_successful_authorize_with_sub_merchant_sub_seller_data
assert_success response
end
+ def test_sending_mcc_on_authorize
+ options = {
+ reference: '345123',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ order_id: '123',
+ mcc: '5411'
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Could not find an acquirer account for the provided currency (USD).', response.message
+ end
+
def test_successful_authorize_with_level_2_data
level_2_data = {
total_tax_amount: '160',
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index e0355cfba68..ff9b0fb657f 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -1473,9 +1473,10 @@ def test_additional_data_lodging
def test_additional_extra_data
response = stub_comms do
- @gateway.authorize(@amount, @credit_card, @options.merge(store: 'test store'))
+ @gateway.authorize(@amount, @credit_card, @options.merge(store: 'test store', mcc: '1234'))
end.check_request do |_endpoint, data, _headers|
assert_equal JSON.parse(data)['store'], 'test store'
+ assert_equal JSON.parse(data)['mcc'], '1234'
end.respond_with(successful_authorize_response)
assert_success response
end
From 1caae49693fe2bb8364e52182754b9e78b8f8c7d Mon Sep 17 00:00:00 2001
From: aenand
Date: Mon, 17 Apr 2023 10:15:23 -0400
Subject: [PATCH 205/390] Close stale issues/PRs
The ActiveMerchant repository often has old issues
and PRs that have gone stale. This makes it hard
to keep track of new requests. This commit adds
the `stale` GHA in debug only mode to have a dry run
of auto-closing issues and PRs.
Issues and PRs (excluding draft PRs) will be able to live for 60 days
without activity before being marked stale, and stale ones will be
closed after 14 days.
Docs: https://github.com/marketplace/actions/close-stale-issues
---
.github/workflows/stale.yml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 .github/workflows/stale.yml
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..c29aa932c1f
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,20 @@
+name: 'Close stale issues and PRs'
+on:
+ schedule:
+ - cron: '30 1 * * *'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v8
+ with:
+ stale-issue-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
+ stale-pr-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
+ days-before-close: 14
+ exempt-draft-pr: true
+ debug-only: true
\ No newline at end of file
From 28d1eb5b80dad94b755657da105d18e69e095f90 Mon Sep 17 00:00:00 2001
From: David Perry
Date: Mon, 30 Oct 2023 10:06:22 -0400
Subject: [PATCH 206/390] Activate stale PR/issue GHA script
Removes debug-only flag from stale.yml to let it actually close items.
---
.github/workflows/stale.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index c29aa932c1f..5ad2e57628a 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -16,5 +16,4 @@ jobs:
stale-issue-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
stale-pr-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
days-before-close: 14
- exempt-draft-pr: true
- debug-only: true
\ No newline at end of file
+ exempt-draft-pr: true
\ No newline at end of file
From 6a4afac96bc95da34c38c638d7a34ea575a1bbd7 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 27 Oct 2023 14:26:22 -0500
Subject: [PATCH 207/390] Quickbooks: Remove raise OAuth from
extract_response_body_or_raise
The OAuth response in extract_response_body_or_raise is preventing
the transaction from being retried if they failed with
AuthenticationFailed because of invalid credentials or expired access
tokens.
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/quickbooks.rb | 3 ---
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5d1033d0e6a..4a072f0ab44 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -44,6 +44,7 @@
* GlobalCollect: Added support for 3DS exemption request field [almalee24] #4917
* NMI: Update supported countries list [jcreiff] #4931
* Adyen: Add mcc field [jcreiff] #4926
+* Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 6197581bbe7..e7e2cba0018 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -364,9 +364,6 @@ def extract_response_body_or_raise(response_error)
raise response_error
end
- error_code = JSON.parse(response_error.response.body)['code']
- raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'
-
response_error.response.body
end
From b102bbc02f99ce1f9a1bb0a2b797b2dd959766aa Mon Sep 17 00:00:00 2001
From: Rachel Kirk
Date: Fri, 27 Oct 2023 16:29:39 -0400
Subject: [PATCH 208/390] Kushki: Add support for Partial Refunds
This updates the logic to allow a request body to be sent as part of a refund request. Kushki uses the DELETE http method for refunds and voids, which meant that they were truly reference transactions only without a request body. The ssl_invoke method needed to be updated to allow for this, and still be able to send the ticketNumber through in post to be captured and added to the url.
This also updates remote tests with PEN currency to use credentials that are specific to Peru to allow them to pass.
Unit Tests:
19 tests, 117 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote Tests:
25 tests, 74 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96% passed
Local Tests:
5652 tests, 78252 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
.../billing/gateways/kushki.rb | 16 ++++---
test/remote/gateways/remote_kushki_test.rb | 33 +++++++++++---
test/unit/gateways/kushki_test.rb | 43 +++++++++++++++++++
3 files changed, 80 insertions(+), 12 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index fbdae802e96..7b9d52c20b3 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -48,8 +48,9 @@ def refund(amount, authorization, options = {})
post = {}
post[:ticketNumber] = authorization
add_full_response(post, options)
+ add_invoice(action, post, amount, options)
- commit(action, post)
+ commit(action, post, options)
end
def void(authorization, options = {})
@@ -185,7 +186,8 @@ def add_contact_details(post, contact_details_options)
end
def add_full_response(post, options)
- post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
+ # this is the only currently accepted value for this field, previously it was 'true'
+ post[:fullResponse] = 'v2' unless options[:full_response] == 'false' || options[:full_response].blank?
end
def add_metadata(post, options)
@@ -245,10 +247,10 @@ def add_three_d_secure(post, payment_method, options)
'capture' => 'capture'
}
- def commit(action, params)
+ def commit(action, params, options = {})
response =
begin
- parse(ssl_invoke(action, params))
+ parse(ssl_invoke(action, params, options))
rescue ResponseError => e
parse(e.response.body)
end
@@ -265,9 +267,11 @@ def commit(action, params)
)
end
- def ssl_invoke(action, params)
+ def ssl_invoke(action, params, options)
if %w[void refund].include?(action)
- ssl_request(:delete, url(action, params), nil, headers(action))
+ # removes ticketNumber from request for partial refunds because gateway will reject if included in request body
+ data = options[:partial_refund] == true ? post_data(params.except(:ticketNumber)) : nil
+ ssl_request(:delete, url(action, params), data, headers(action))
else
ssl_post(url(action, params), post_data(params), headers(action))
end
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index 14ab10b18c9..8527c769aea 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -3,6 +3,7 @@
class RemoteKushkiTest < Test::Unit::TestCase
def setup
@gateway = KushkiGateway.new(fixtures(:kushki))
+ @gateway_partial_refund = KushkiGateway.new(fixtures(:kushki_partial))
@amount = 100
@credit_card = credit_card('4000100011112224', verification_value: '777')
@declined_card = credit_card('4000300011112220')
@@ -144,7 +145,7 @@ def test_failed_purchase
end
def test_successful_authorize
- response = @gateway.authorize(@amount, @credit_card, { currency: 'PEN' })
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, { currency: 'PEN' })
assert_success response
assert_equal 'Succeeded', response.message
assert_match %r(^\d+$), response.authorization
@@ -189,7 +190,7 @@ def test_successful_3ds2_authorize_with_visa_card
eci: '07'
}
}
- response = @gateway.authorize(@amount, @credit_card, options)
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
assert_match %r(^\d+$), response.authorization
@@ -204,7 +205,7 @@ def test_successful_3ds2_authorize_with_visa_card_with_optional_xid
eci: '07'
}
}
- response = @gateway.authorize(@amount, @credit_card, options)
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
assert_match %r(^\d+$), response.authorization
@@ -222,7 +223,7 @@ def test_successful_3ds2_authorize_with_master_card
}
credit_card = credit_card('5223450000000007', brand: 'master', verification_value: '777')
- response = @gateway.authorize(@amount, credit_card, options)
+ response = @gateway_partial_refund.authorize(@amount, credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
end
@@ -254,7 +255,7 @@ def test_failed_3ds2_authorize
xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE='
}
}
- response = @gateway.authorize(@amount, @credit_card, options)
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
assert_failure response
assert_equal 'K001', response.responses.last.error_code
end
@@ -270,7 +271,7 @@ def test_failed_3ds2_authorize_with_different_card
}
credit_card = credit_card('6011111111111117', brand: 'discover', verification_value: '777')
assert_raise ArgumentError do
- @gateway.authorize(@amount, credit_card, options)
+ @gateway_partial_refund.authorize(@amount, credit_card, options)
end
end
@@ -316,6 +317,26 @@ def test_failed_refund
assert_equal 'Missing Authentication Token', refund.message
end
+ # partial refunds are only available in Colombia, Chile, Mexico and Peru
+ def test_partial_refund
+ options = {
+ currency: 'PEN',
+ full_response: 'v2'
+ }
+ purchase = @gateway_partial_refund.purchase(500, @credit_card, options)
+ assert_success purchase
+
+ refund_options = {
+ currency: 'PEN',
+ partial_refund: true,
+ full_response: 'v2'
+ }
+
+ assert refund = @gateway_partial_refund.refund(250, purchase.authorization, refund_options)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
def test_successful_void
purchase = @gateway.purchase(@amount, @credit_card)
assert_success purchase
diff --git a/test/unit/gateways/kushki_test.rb b/test/unit/gateways/kushki_test.rb
index f623b6b4ec9..34f2aab927e 100644
--- a/test/unit/gateways/kushki_test.rb
+++ b/test/unit/gateways/kushki_test.rb
@@ -280,6 +280,49 @@ def test_failed_refund
assert_equal 'K010', refund.error_code
end
+ def test_partial_refund
+ @gateway.expects(:ssl_post).returns(successful_charge_response)
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+
+ options = { currency: 'PEN' }
+
+ purchase = @gateway.purchase(100, @credit_card, options)
+
+ refund = stub_comms(@gateway, :ssl_request) do
+ refund_options = {
+ currency: 'PEN',
+ partial_refund: true,
+ full_response: true
+ }
+ @gateway.refund(50, purchase.authorization, refund_options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['amount']['subtotalIva0'], 0.5
+ end.respond_with(successful_refund_response)
+ assert_success refund
+ end
+
+ def test_full_refund_does_not_have_request_body
+ @gateway.expects(:ssl_post).returns(successful_charge_response)
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+
+ options = { currency: 'PEN' }
+
+ purchase = @gateway.purchase(@amount, @credit_card, options)
+ assert_success purchase
+
+ refund = stub_comms(@gateway, :ssl_request) do
+ refund_options = {
+ currency: 'PEN',
+ full_response: true
+ }
+ @gateway.refund(@amount, purchase.authorization, refund_options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_nil(data)
+ end.respond_with(successful_refund_response)
+ assert_success refund
+ end
+
def test_successful_capture
@gateway.expects(:ssl_post).returns(successful_authorize_response)
@gateway.expects(:ssl_post).returns(successful_token_response)
From 7a57f17bfb34d844150820a62693448d38d5a989 Mon Sep 17 00:00:00 2001
From: Gustavo Sanmartin
Date: Thu, 2 Nov 2023 14:34:46 -0500
Subject: [PATCH 209/390] Fix token nonce to support nil billing address
(#4938)
Co-authored-by: Gustavo Sanmartin
---
.../billing/gateways/braintree/token_nonce.rb | 2 +-
.../gateways/braintree_token_nonce_test.rb | 18 ++++++++++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
index eeaee734fc2..dc9a3e0bc90 100644
--- a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
+++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
@@ -92,7 +92,7 @@ def billing_address_from_options
def build_nonce_credit_card_request(payment_method)
billing_address = billing_address_from_options
key_replacements = { city: :locality, state: :region, zipCode: :postalCode }
- billing_address.transform_keys! { |key| key_replacements[key] || key }
+ billing_address&.transform_keys! { |key| key_replacements[key] || key }
{
creditCard: {
number: payment_method.number,
diff --git a/test/unit/gateways/braintree_token_nonce_test.rb b/test/unit/gateways/braintree_token_nonce_test.rb
index d75ff5c405e..89aac7612c8 100644
--- a/test/unit/gateways/braintree_token_nonce_test.rb
+++ b/test/unit/gateways/braintree_token_nonce_test.rb
@@ -25,6 +25,7 @@ def setup
ach_mandate: 'ach_mandate'
}
@generator = TokenNonce.new(@braintree_backend, @options)
+ @no_address_generator = TokenNonce.new(@braintree_backend, { ach_mandate: 'ach_mandate' })
end
def test_build_nonce_request_for_credit_card
@@ -66,6 +67,23 @@ def test_build_nonce_request_for_bank_account
assert_equal bank_account_input['individualOwner']['lastName'], bank_account.last_name
end
+ def test_build_nonce_request_for_credit_card_without_address
+ credit_card = credit_card('4111111111111111')
+ response = @no_address_generator.send(:build_nonce_request, credit_card)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
+ assert_includes parse_response['variables']['input'], 'creditCard'
+
+ credit_card_input = parse_response['variables']['input']['creditCard']
+
+ assert_equal credit_card_input['number'], credit_card.number
+ assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
+ assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
+ assert_equal credit_card_input['cvv'], credit_card.verification_value
+ assert_equal credit_card_input['cardholderName'], credit_card.name
+ end
+
def test_token_from
credit_card = credit_card(number: 4111111111111111)
c_token = @generator.send(:token_from, credit_card, token_credit_response)
From 6580e97c12b8b629303656e42c70c0d6d799808e Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Fri, 3 Nov 2023 08:27:01 -0500
Subject: [PATCH 210/390] Cecabank: Add new Cecabank gateway to use the JSON
REST API (#4920)
Description
-------------------------
Cecabank updated their API to support JSON endpoints and would like to be able to support Authorize, Purchase, Void, Capture, Refund/Credit and stored_credentials
Note: We include an attribute for 3ds global to be able to test with Cecabank endpoints
For Spreedly reference:
SER-877
SER-859
Unit test
-------------------------
Finished in 33.01198 seconds.
5647 tests, 78238 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
171.06 tests/s, 2369.99 assertions/s
Rubocop
-------------------------
778 files inspected, no offenses detected
Co-authored-by: Luis
---
CHANGELOG | 1 +
.../billing/credit_card_formatting.rb | 4 +
.../billing/gateways/cecabank.rb | 247 +-----------------
.../gateways/cecabank/cecabank_common.rb | 32 +++
.../gateways/cecabank/cecabank_json.rb | 231 ++++++++++++++++
.../billing/gateways/cecabank/cecabank_xml.rb | 224 ++++++++++++++++
test/fixtures.yml | 3 +-
.../remote_cecabank_rest_json_test.rb | 181 +++++++++++++
test/remote/gateways/remote_cecabank_test.rb | 4 +-
test/unit/gateways/cecabank_rest_json_test.rb | 224 ++++++++++++++++
test/unit/gateways/cecabank_test.rb | 4 +-
11 files changed, 910 insertions(+), 245 deletions(-)
create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
create mode 100644 test/remote/gateways/remote_cecabank_rest_json_test.rb
create mode 100644 test/unit/gateways/cecabank_rest_json_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index 4a072f0ab44..5088e0c191a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -45,6 +45,7 @@
* NMI: Update supported countries list [jcreiff] #4931
* Adyen: Add mcc field [jcreiff] #4926
* Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935
+* Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/credit_card_formatting.rb b/lib/active_merchant/billing/credit_card_formatting.rb
index ef8a6894ba6..d91d1dba38a 100644
--- a/lib/active_merchant/billing/credit_card_formatting.rb
+++ b/lib/active_merchant/billing/credit_card_formatting.rb
@@ -5,6 +5,10 @@ def expdate(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
+ def strftime_yyyymm(credit_card)
+ format(credit_card.year, :four_digits) + format(credit_card.month, :two_digits)
+ end
+
# This method is used to format numerical information pertaining to credit cards.
#
# format(2005, :two_digits) # => "05"
diff --git a/lib/active_merchant/billing/gateways/cecabank.rb b/lib/active_merchant/billing/gateways/cecabank.rb
index d85b48f7ed9..18a0aed5d93 100644
--- a/lib/active_merchant/billing/gateways/cecabank.rb
+++ b/lib/active_merchant/billing/gateways/cecabank.rb
@@ -1,248 +1,15 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_xml'
+require 'active_merchant/billing/gateways/cecabank/cecabank_json'
+
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class CecabankGateway < Gateway
- self.test_url = 'https://tpv.ceca.es'
- self.live_url = 'https://pgw.ceca.es'
-
- self.supported_countries = ['ES']
- self.supported_cardtypes = %i[visa master american_express]
- self.homepage_url = 'http://www.ceca.es/es/'
- self.display_name = 'Cecabank'
- self.default_currency = 'EUR'
- self.money_format = :cents
-
- #### CECA's MAGIC NUMBERS
- CECA_NOTIFICATIONS_URL = 'NONE'
- CECA_ENCRIPTION = 'SHA2'
- CECA_DECIMALS = '2'
- CECA_MODE = 'SSL'
- CECA_UI_LESS_LANGUAGE = 'XML'
- CECA_UI_LESS_LANGUAGE_REFUND = '1'
- CECA_UI_LESS_REFUND_PAGE = 'anulacion_xml'
- CECA_ACTION_REFUND = 'anulaciones/anularParcial' # use partial refund's URL to avoid time frame limitations and decision logic on client side
- CECA_ACTION_PURCHASE = 'tpv/compra'
- CECA_CURRENCIES_DICTIONARY = { 'EUR' => 978, 'USD' => 840, 'GBP' => 826 }
-
- # Creates a new CecabankGateway
- #
- # The gateway requires four values for connection to be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :merchant_id -- Cecabank's merchant_id (REQUIRED)
- # * :acquirer_bin -- Cecabank's acquirer_bin (REQUIRED)
- # * :terminal_id -- Cecabank's terminal_id (REQUIRED)
- # * :key -- Cecabank's cypher key (REQUIRED)
- # * :test -- +true+ or +false+. If true, perform transactions against the test server.
- # Otherwise, perform transactions against the production server.
- def initialize(options = {})
- requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :key)
- super
- end
-
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
- #
- # ==== Parameters
- #
- # * money -- The amount to be purchased as an Integer value in cents.
- # * creditcard -- The CreditCard details for the transaction.
- # * options -- A hash of optional parameters.
- #
- # ==== Options
- #
- # * :order_id -- order_id passed used purchase. (REQUIRED)
- # * :currency -- currency. Supported: EUR, USD, GBP.
- # * :description -- description to be pased to the gateway.
- def purchase(money, creditcard, options = {})
- requires!(options, :order_id)
-
- post = { 'Descripcion' => options[:description],
- 'Num_operacion' => options[:order_id],
- 'Idioma' => CECA_UI_LESS_LANGUAGE,
- 'Pago_soportado' => CECA_MODE,
- 'URL_OK' => CECA_NOTIFICATIONS_URL,
- 'URL_NOK' => CECA_NOTIFICATIONS_URL,
- 'Importe' => amount(money),
- 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)] }
-
- add_creditcard(post, creditcard)
-
- commit(CECA_ACTION_PURCHASE, post)
- end
-
- # Refund a transaction.
- #
- # This transaction indicates to the gateway that
- # money should flow from the merchant to the customer.
- #
- # ==== Parameters
- #
- # * money -- The amount to be credited to the customer as an Integer value in cents.
- # * identification -- The reference given from the gateway on purchase (reference, not operation).
- # * options -- A hash of parameters.
- def refund(money, identification, options = {})
- reference, order_id = split_authorization(identification)
-
- post = { 'Referencia' => reference,
- 'Num_operacion' => order_id,
- 'Idioma' => CECA_UI_LESS_LANGUAGE_REFUND,
- 'Pagina' => CECA_UI_LESS_REFUND_PAGE,
- 'Importe' => amount(money),
- 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)] }
-
- commit(CECA_ACTION_REFUND, post)
- end
-
- def supports_scrubbing
- true
- end
-
- def scrub(transcript)
- transcript.
- gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
- gsub(%r((&?pan=)[^&]*)i, '\1[FILTERED]').
- gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
- end
-
- private
-
- def add_creditcard(post, creditcard)
- post['PAN'] = creditcard.number
- post['Caducidad'] = expdate(creditcard)
- post['CVV2'] = creditcard.verification_value
- post['Pago_elegido'] = CECA_MODE
- end
+ self.abstract_class = true
- def expdate(creditcard)
- "#{format(creditcard.year, :four_digits)}#{format(creditcard.month, :two_digits)}"
- end
-
- def parse(body)
- response = {}
-
- root = REXML::Document.new(body).root
-
- response[:success] = (root.attributes['valor'] == 'OK')
- response[:date] = root.attributes['fecha']
- response[:operation_number] = root.attributes['numeroOperacion']
- response[:message] = root.attributes['valor']
-
- if root.elements['OPERACION']
- response[:operation_type] = root.elements['OPERACION'].attributes['tipo']
- response[:amount] = root.elements['OPERACION/importe'].text.strip
- end
-
- response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion']
- response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
- response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia']
- response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan']
-
- if root.elements['ERROR']
- response[:error_code] = root.elements['ERROR/codigo'].text
- response[:error_message] = root.elements['ERROR/descripcion'].text
- else
- if root.elements['OPERACION'].attributes['numeroOperacion'] == '000'
- response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
- else
- response[:authorization] = root.attributes['numeroOperacion']
- end
- end
-
- return response
- rescue REXML::ParseException => e
- response[:success] = false
- response[:message] = 'Unable to parse the response.'
- response[:error_message] = e.message
- response
- end
-
- def commit(action, parameters)
- parameters.merge!(
- 'Cifrado' => CECA_ENCRIPTION,
- 'Firma' => generate_signature(action, parameters),
- 'Exponente' => CECA_DECIMALS,
- 'MerchantID' => options[:merchant_id],
- 'AcquirerBIN' => options[:acquirer_bin],
- 'TerminalID' => options[:terminal_id]
- )
- url = (test? ? self.test_url : self.live_url) + "/tpvweb/#{action}.action"
- xml = ssl_post("#{url}?", post_data(parameters))
- response = parse(xml)
- Response.new(
- response[:success],
- message_from(response),
- response,
- test: test?,
- authorization: build_authorization(response),
- error_code: response[:error_code]
- )
- end
-
- def message_from(response)
- if response[:message] == 'ERROR' && response[:error_message]
- response[:error_message]
- elsif response[:error_message]
- "#{response[:message]} #{response[:error_message]}"
- else
- response[:message]
- end
- end
-
- def post_data(params)
- return nil unless params
-
- params.map do |key, value|
- next if value.blank?
-
- if value.is_a?(Hash)
- h = {}
- value.each do |k, v|
- h["#{key}.#{k}"] = v unless v.blank?
- end
- post_data(h)
- else
- "#{key}=#{CGI.escape(value.to_s)}"
- end
- end.compact.join('&')
- end
-
- def build_authorization(response)
- [response[:reference], response[:authorization]].join('|')
- end
-
- def split_authorization(authorization)
- authorization.split('|')
- end
+ def self.new(options = {})
+ return CecabankJsonGateway.new(options) if options[:is_rest_json]
- def generate_signature(action, parameters)
- signature_fields =
- case action
- when CECA_ACTION_REFUND
- options[:key].to_s +
- options[:merchant_id].to_s +
- options[:acquirer_bin].to_s +
- options[:terminal_id].to_s +
- parameters['Num_operacion'].to_s +
- parameters['Importe'].to_s +
- parameters['TipoMoneda'].to_s +
- CECA_DECIMALS +
- parameters['Referencia'].to_s +
- CECA_ENCRIPTION
- else
- options[:key].to_s +
- options[:merchant_id].to_s +
- options[:acquirer_bin].to_s +
- options[:terminal_id].to_s +
- parameters['Num_operacion'].to_s +
- parameters['Importe'].to_s +
- parameters['TipoMoneda'].to_s +
- CECA_DECIMALS +
- CECA_ENCRIPTION +
- CECA_NOTIFICATIONS_URL +
- CECA_NOTIFICATIONS_URL
- end
- Digest::SHA2.hexdigest(signature_fields)
+ CecabankXmlGateway.new(options)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
new file mode 100644
index 00000000000..d71268649d6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
@@ -0,0 +1,32 @@
+module CecabankCommon
+ #### CECA's MAGIC NUMBERS
+ CECA_ENCRIPTION = 'SHA2'
+ CECA_CURRENCIES_DICTIONARY = { 'EUR' => 978, 'USD' => 840, 'GBP' => 826 }
+
+ def self.included(base)
+ base.supported_countries = ['ES']
+ base.supported_cardtypes = %i[visa master american_express]
+ base.homepage_url = 'http://www.ceca.es/es/'
+ base.display_name = 'Cecabank'
+ base.default_currency = 'EUR'
+ base.money_format = :cents
+ end
+
+ # Creates a new CecabankGateway
+ #
+ # The gateway requires four values for connection to be passed
+ # in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * :merchant_id -- Cecabank's merchant_id (REQUIRED)
+ # * :acquirer_bin -- Cecabank's acquirer_bin (REQUIRED)
+ # * :terminal_id -- Cecabank's terminal_id (REQUIRED)
+ # * :cypher_key -- Cecabank's cypher key (REQUIRED)
+ # * :test -- +true+ or +false+. If true, perform transactions against the test server.
+ # Otherwise, perform transactions against the production server.
+ def initialize(options = {})
+ requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :cypher_key)
+ super
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
new file mode 100644
index 00000000000..dc829c9e964
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
@@ -0,0 +1,231 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_common'
+
+module ActiveMerchant
+ module Billing
+ class CecabankJsonGateway < Gateway
+ include CecabankCommon
+
+ CECA_ACTIONS_DICTIONARY = {
+ purchase: :REST_AUTORIZACION,
+ authorize: :REST_PREAUTORIZACION,
+ capture: :REST_COBRO_PREAUTORIZACION,
+ refund: :REST_DEVOLUCION,
+ void: :REST_ANULACION
+ }.freeze
+
+ CECA_REASON_TYPES = {
+ installment: :I,
+ recurring: :R,
+ unscheduled: :C
+ }.freeze
+
+ CECA_INITIATOR = {
+ merchant: :N,
+ cardholder: :S
+ }.freeze
+
+ self.test_url = 'https://tpv.ceca.es/tpvweb/rest/procesos/'
+ self.live_url = 'https://pgw.ceca.es/tpvweb/rest/procesos/'
+
+ def authorize(money, creditcard, options = {})
+ handle_purchase(:authorize, money, creditcard, options)
+ end
+
+ def capture(money, identification, options = {})
+ authorization, operation_number, _network_transaction_id = identification.split('#')
+
+ post = {}
+ options[:operation_number] = operation_number
+ add_auth_invoice_data(:capture, post, money, authorization, options)
+
+ commit('compra', post)
+ end
+
+ def purchase(money, creditcard, options = {})
+ handle_purchase(:purchase, money, creditcard, options)
+ end
+
+ def void(identification, options = {})
+ authorization, operation_number, money, _network_transaction_id = identification.split('#')
+ options[:operation_number] = operation_number
+ handle_cancellation(:void, money.to_i, authorization, options)
+ end
+
+ def refund(money, identification, options = {})
+ authorization, operation_number, _money, _network_transaction_id = identification.split('#')
+ options[:operation_number] = operation_number
+ handle_cancellation(:refund, money, authorization, options)
+ end
+
+ private
+
+ def handle_purchase(action, money, creditcard, options)
+ post = { parametros: { accion: CECA_ACTIONS_DICTIONARY[action] } }
+
+ add_invoice(post, money, options)
+ add_creditcard(post, creditcard)
+ add_stored_credentials(post, creditcard, options)
+ add_three_d_secure(post, options)
+
+ commit('compra', post)
+ end
+
+ def handle_cancellation(action, money, authorization, options = {})
+ post = {}
+ add_auth_invoice_data(action, post, money, authorization, options)
+
+ commit('anulacion', post)
+ end
+
+ def add_auth_invoice_data(action, post, money, authorization, options)
+ params = post[:parametros] ||= {}
+ params[:accion] = CECA_ACTIONS_DICTIONARY[action]
+ params[:referencia] = authorization
+
+ add_invoice(post, money, options)
+ end
+
+ def add_encryption(post)
+ post[:cifrado] = CECA_ENCRIPTION
+ end
+
+ def add_signature(post, params_encoded, options)
+ post[:firma] = Digest::SHA2.hexdigest(@options[:cypher_key].to_s + params_encoded)
+ end
+
+ def add_merchant_data(post)
+ params = post[:parametros] ||= {}
+
+ params[:merchantID] = @options[:merchant_id]
+ params[:acquirerBIN] = @options[:acquirer_bin]
+ params[:terminalID] = @options[:terminal_id]
+ end
+
+ def add_invoice(post, money, options)
+ post[:parametros][:numOperacion] = options[:operation_number] || options[:order_id]
+ post[:parametros][:importe] = amount(money)
+ post[:parametros][:tipoMoneda] = CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)].to_s
+ post[:parametros][:exponente] = 2.to_s
+ end
+
+ def add_creditcard(post, creditcard)
+ params = post[:parametros] ||= {}
+
+ params[:pan] = creditcard.number
+ params[:caducidad] = strftime_yyyymm(creditcard)
+ params[:cvv2] = creditcard.verification_value
+ params[:csc] = creditcard.verification_value if CreditCard.brand?(creditcard.number) == 'american_express'
+ end
+
+ def add_stored_credentials(post, creditcard, options)
+ return unless stored_credential = options[:stored_credential]
+
+ return if options[:exemption_sca] == 'NONE' || options[:exemption_sca].blank?
+
+ params = post[:parametros] ||= {}
+ params[:exencionSCA] = options[:exemption_sca]
+
+ if options[:exemption_sca] == 'MIT'
+ requires!(stored_credential, :reason_type, :initiator)
+ requires!(options, :recurring_frequency)
+ end
+
+ params[:tipoCOF] = CECA_REASON_TYPES[stored_credential[:reason_type].to_sym]
+ params[:inicioRec] = CECA_INITIATOR[stored_credential[:initiator].to_sym]
+ params[:finRec] = options[:recurring_end_date] || strftime_yyyymm(creditcard)
+ params[:frecRec] = options[:recurring_frequency]
+
+ params[:mmppTxId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ end
+
+ def add_three_d_secure(post, options)
+ params = post[:parametros] ||= {}
+
+ params[:ThreeDsResponse] = options[:three_d_secure] if options[:three_d_secure]
+ end
+
+ def commit(action, post, method = :post)
+ auth_options = {
+ operation_number: post[:parametros][:numOperacion],
+ amount: post[:parametros][:importe]
+ }
+
+ add_encryption(post)
+ add_merchant_data(post)
+
+ params_encoded = encode_params(post)
+ add_signature(post, params_encoded, options)
+
+ response = parse(ssl_request(method, url(action), post.to_json, headers))
+ response[:parametros] = parse(response[:parametros]) if response[:parametros]
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, auth_options),
+ network_transaction_id: network_transaction_id_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def url(action)
+ (test? ? self.test_url : self.live_url) + action
+ end
+
+ def host
+ URI.parse(url('')).host
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Host' => host
+ }
+ end
+
+ def parse(string)
+ JSON.parse(string).with_indifferent_access
+ rescue JSON::ParserError
+ parse(decode_params(string))
+ end
+
+ def encode_params(post)
+ post[:parametros] = Base64.strict_encode64(post[:parametros].to_json)
+ end
+
+ def decode_params(params)
+ Base64.decode64(params)
+ end
+
+ def success_from(response)
+ response[:codResult].blank?
+ end
+
+ def message_from(response)
+ return response[:parametros].to_json if success_from(response)
+
+ response[:paramsEntradaError] || response[:idProceso]
+ end
+
+ def authorization_from(response, auth_options = {})
+ return unless response[:parametros]
+
+ [
+ response[:parametros][:referencia],
+ auth_options[:operation_number],
+ auth_options[:amount]
+ ].join('#')
+ end
+
+ def network_transaction_id_from(response)
+ response.dig(:parametros, :mmppTxId)
+ end
+
+ def error_code_from(response)
+ (response[:codResult] || :paramsEntradaError) unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
new file mode 100644
index 00000000000..ba9f727d98f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
@@ -0,0 +1,224 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_common'
+
+module ActiveMerchant
+ module Billing
+ class CecabankXmlGateway < Gateway
+ include CecabankCommon
+
+ self.test_url = 'https://tpv.ceca.es'
+ self.live_url = 'https://pgw.ceca.es'
+
+ #### CECA's MAGIC NUMBERS
+ CECA_NOTIFICATIONS_URL = 'NONE'
+ CECA_DECIMALS = '2'
+ CECA_MODE = 'SSL'
+ CECA_UI_LESS_LANGUAGE = 'XML'
+ CECA_UI_LESS_LANGUAGE_REFUND = '1'
+ CECA_UI_LESS_REFUND_PAGE = 'anulacion_xml'
+ CECA_ACTION_REFUND = 'anulaciones/anularParcial' # use partial refund's URL to avoid time frame limitations and decision logic on client side
+ CECA_ACTION_PURCHASE = 'tpv/compra'
+
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be purchased as an Integer value in cents.
+ # * creditcard -- The CreditCard details for the transaction.
+ # * options -- A hash of optional parameters.
+ #
+ # ==== Options
+ #
+ # * :order_id -- order_id passed used purchase. (REQUIRED)
+ # * :currency -- currency. Supported: EUR, USD, GBP.
+ # * :description -- description to be pased to the gateway.
+ def purchase(money, creditcard, options = {})
+ requires!(options, :order_id)
+
+ post = { 'Descripcion' => options[:description],
+ 'Num_operacion' => options[:order_id],
+ 'Idioma' => CECA_UI_LESS_LANGUAGE,
+ 'Pago_soportado' => CECA_MODE,
+ 'URL_OK' => CECA_NOTIFICATIONS_URL,
+ 'URL_NOK' => CECA_NOTIFICATIONS_URL,
+ 'Importe' => amount(money),
+ 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)] }
+
+ add_creditcard(post, creditcard)
+
+ commit(CECA_ACTION_PURCHASE, post)
+ end
+
+ # Refund a transaction.
+ #
+ # This transaction indicates to the gateway that
+ # money should flow from the merchant to the customer.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be credited to the customer as an Integer value in cents.
+ # * identification -- The reference given from the gateway on purchase (reference, not operation).
+ # * options -- A hash of parameters.
+ def refund(money, identification, options = {})
+ reference, order_id = split_authorization(identification)
+
+ post = { 'Referencia' => reference,
+ 'Num_operacion' => order_id,
+ 'Idioma' => CECA_UI_LESS_LANGUAGE_REFUND,
+ 'Pagina' => CECA_UI_LESS_REFUND_PAGE,
+ 'Importe' => amount(money),
+ 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)] }
+
+ commit(CECA_ACTION_REFUND, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?pan=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_creditcard(post, creditcard)
+ post['PAN'] = creditcard.number
+ post['Caducidad'] = expdate(creditcard)
+ post['CVV2'] = creditcard.verification_value
+ post['Pago_elegido'] = CECA_MODE
+ end
+
+ def expdate(creditcard)
+ "#{format(creditcard.year, :four_digits)}#{format(creditcard.month, :two_digits)}"
+ end
+
+ def parse(body)
+ response = {}
+
+ root = REXML::Document.new(body).root
+
+ response[:success] = (root.attributes['valor'] == 'OK')
+ response[:date] = root.attributes['fecha']
+ response[:operation_number] = root.attributes['numeroOperacion']
+ response[:message] = root.attributes['valor']
+
+ if root.elements['OPERACION']
+ response[:operation_type] = root.elements['OPERACION'].attributes['tipo']
+ response[:amount] = root.elements['OPERACION/importe'].text.strip
+ end
+
+ response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion']
+ response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
+ response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia']
+ response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan']
+
+ if root.elements['ERROR']
+ response[:error_code] = root.elements['ERROR/codigo'].text
+ response[:error_message] = root.elements['ERROR/descripcion'].text
+ elsif root.elements['OPERACION'].attributes['numeroOperacion'] == '000'
+ response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
+ else
+ response[:authorization] = root.attributes['numeroOperacion']
+ end
+
+ return response
+ rescue REXML::ParseException => e
+ response[:success] = false
+ response[:message] = 'Unable to parse the response.'
+ response[:error_message] = e.message
+ response
+ end
+
+ def commit(action, parameters)
+ parameters.merge!(
+ 'Cifrado' => CECA_ENCRIPTION,
+ 'Firma' => generate_signature(action, parameters),
+ 'Exponente' => CECA_DECIMALS,
+ 'MerchantID' => options[:merchant_id],
+ 'AcquirerBIN' => options[:acquirer_bin],
+ 'TerminalID' => options[:terminal_id]
+ )
+ url = (test? ? self.test_url : self.live_url) + "/tpvweb/#{action}.action"
+ xml = ssl_post("#{url}?", post_data(parameters))
+ response = parse(xml)
+ Response.new(
+ response[:success],
+ message_from(response),
+ response,
+ test: test?,
+ authorization: build_authorization(response),
+ error_code: response[:error_code]
+ )
+ end
+
+ def message_from(response)
+ if response[:message] == 'ERROR' && response[:error_message]
+ response[:error_message]
+ elsif response[:error_message]
+ "#{response[:message]} #{response[:error_message]}"
+ else
+ response[:message]
+ end
+ end
+
+ def post_data(params)
+ return nil unless params
+
+ params.map do |key, value|
+ next if value.blank?
+
+ if value.is_a?(Hash)
+ h = {}
+ value.each do |k, v|
+ h["#{key}.#{k}"] = v unless v.blank?
+ end
+ post_data(h)
+ else
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end
+ end.compact.join('&')
+ end
+
+ def build_authorization(response)
+ [response[:reference], response[:authorization]].join('|')
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def generate_signature(action, parameters)
+ signature_fields =
+ case action
+ when CECA_ACTION_REFUND
+ options[:signature_key].to_s +
+ options[:merchant_id].to_s +
+ options[:acquirer_bin].to_s +
+ options[:terminal_id].to_s +
+ parameters['Num_operacion'].to_s +
+ parameters['Importe'].to_s +
+ parameters['TipoMoneda'].to_s +
+ CECA_DECIMALS +
+ parameters['Referencia'].to_s +
+ CECA_ENCRIPTION
+ else
+ options[:signature_key].to_s +
+ options[:merchant_id].to_s +
+ options[:acquirer_bin].to_s +
+ options[:terminal_id].to_s +
+ parameters['Num_operacion'].to_s +
+ parameters['Importe'].to_s +
+ parameters['TipoMoneda'].to_s +
+ CECA_DECIMALS +
+ CECA_ENCRIPTION +
+ CECA_NOTIFICATIONS_URL +
+ CECA_NOTIFICATIONS_URL
+ end
+ Digest::SHA2.hexdigest(signature_fields)
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 43c848a643a..2acfa7b16cc 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -178,7 +178,8 @@ cecabank:
merchant_id: MERCHANTID
acquirer_bin: ACQUIRERBIN
terminal_id: TERMINALID
- key: KEY
+ signature_key: KEY
+ is_rest_json: true
cenpos:
merchant_id: SOMECREDENTIAL
diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb
new file mode 100644
index 00000000000..582560215ad
--- /dev/null
+++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb
@@ -0,0 +1,181 @@
+require 'test_helper'
+
+class RemoteCecabankTest < Test::Unit::TestCase
+ def setup
+ @gateway = CecabankJsonGateway.new(fixtures(:cecabank))
+
+ @amount = 100
+ @credit_card = credit_card('4507670001000009', { month: 12, year: Time.now.year, verification_value: '989' })
+ @declined_card = credit_card('5540500001000004', { month: 11, year: Time.now.year + 1, verification_value: '001' })
+
+ @options = {
+ order_id: generate_unique_id,
+ exemption_sca: 'NONE',
+ three_d_secure: three_d_secure
+ }
+
+ @cit_options = @options.merge({
+ exemption_sca: 'MIT',
+ recurring_end_date: '20231231',
+ recurring_frequency: '1',
+ stored_credential: {
+ reason_type: 'unscheduled',
+ initiator: 'cardholder'
+ }
+ })
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match '106900640', response.message
+ assert_match '1-190', response.error_code
+ end
+
+ def test_successful_capture
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert response = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_capture
+ assert response = @gateway.capture(@amount, 'abc123', @options)
+ assert_failure response
+ assert_match '106900640', response.message
+ assert_match '807', response.error_code
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match '106900640', response.message
+ assert_match '1-190', response.error_code
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert response = @gateway.refund(@amount, purchase.authorization, order_id: @options[:order_id])
+ assert_success response
+ assert_equal %i[acquirerBIN codAut importe merchantID numAut numOperacion pais referencia terminalID tipoOperacion], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_failure response
+ assert_match '106900640', response.message
+ assert_match '15', response.error_code
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ assert response = @gateway.void(authorize.authorization, order_id: @options[:order_id])
+ assert_success response
+ assert_equal %i[acquirerBIN codAut importe merchantID numAut numOperacion pais referencia terminalID tipoOperacion], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_void
+ assert response = @gateway.void('reference', { order_id: generate_unique_id })
+ assert_failure response
+ assert_match '106900640', response.message
+ assert_match '15', response.error_code
+ end
+
+ def test_invalid_login
+ gateway = CecabankGateway.new(fixtures(:cecabank).merge(cypher_key: 'invalid'))
+
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'ERROR AL CALCULAR FIRMA', response.message
+ end
+
+ def test_purchase_using_stored_credential_cit
+ assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_auth_capture_and_stored_credential_cit
+ assert authorize = @gateway.authorize(@amount, @credit_card, @cit_options)
+ assert_success authorize
+ assert_equal authorize.network_transaction_id, '999999999999999'
+
+ assert capture = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success capture
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit
+ @cit_options[:stored_credential][:reason_type] = 'installment'
+ assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
+ assert_success purchase
+
+ options = @cit_options
+ options[:stored_credential][:reason_type] = 'recurring'
+ options[:stored_credential][:initiator] = 'merchant'
+ options[:stored_credential][:network_transaction_id] = purchase.network_transaction_id
+
+ assert purchase2 = @gateway.purchase(@amount, @credit_card, options)
+ assert_success purchase2
+ end
+
+ def test_failure_stored_credential_invalid_cit_transaction_id
+ options = @cit_options
+ options[:stored_credential][:reason_type] = 'recurring'
+ options[:stored_credential][:initiator] = 'merchant'
+ options[:stored_credential][:network_transaction_id] = 'bad_reference'
+
+ assert purchase = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure purchase
+ assert_match '106900640', purchase.message
+ assert_match '810', purchase.error_code
+ end
+
+ private
+
+ def three_d_secure
+ {
+ authentication_value: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
+ email: 'example@example.com',
+ three_ds_version: '2.2.0',
+ directory_server_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
+ exemption_type: 'null',
+ token: 'Gt3wYk5kEaxCON6byEMWYsW58iq',
+ xid: 'xx',
+ authentication_response_status: 'Y',
+ ecommerce_indicator: '02',
+ sca_provider_key: 'W2Y0DOpdImu6AjaWYPituPWkh2C',
+ gateway_transaction_key: 'A7YADMeCYYjG4d9ozKUQiQvpGZp',
+ transaction_type: 'Sca::Authentication',
+ daf: 'false',
+ challenge_form: '',
+ acs_transaction_id: '18c353b0-76e3-4a4c-8033-f14fe9ce39dc',
+ updated_at: '2023-08-29T08:03:40Z',
+ three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5',
+ enrolled: 'true',
+ flow_performed: 'challenge',
+ succeeded: true,
+ amount: 11000,
+ ip: '119.119.35.111',
+ directory_response_status: 'C',
+ currency_code: 'ISK',
+ created_at: '2023-08-29T08:02:42Z',
+ state: 'succeeded'
+ }.to_json.to_s
+ end
+end
diff --git a/test/remote/gateways/remote_cecabank_test.rb b/test/remote/gateways/remote_cecabank_test.rb
index d0389ba26fc..217ed8cc501 100644
--- a/test/remote/gateways/remote_cecabank_test.rb
+++ b/test/remote/gateways/remote_cecabank_test.rb
@@ -41,11 +41,11 @@ def test_unsuccessful_refund
assert response = @gateway.refund(@amount, purchase.authorization, @options.merge(currency: 'USD'))
assert_failure response
- assert_match 'ERROR', response.message
+ assert_match 'Error', response.message
end
def test_invalid_login
- gateway = CecabankGateway.new(fixtures(:cecabank).merge(key: 'invalid'))
+ gateway = CecabankGateway.new(fixtures(:cecabank).merge(signature_key: 'invalid'))
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb
new file mode 100644
index 00000000000..98b34b5963b
--- /dev/null
+++ b/test/unit/gateways/cecabank_rest_json_test.rb
@@ -0,0 +1,224 @@
+require 'test_helper'
+
+class CecabankJsonTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = CecabankJsonGateway.new(
+ merchant_id: '12345678',
+ acquirer_bin: '12345678',
+ terminal_id: '00000003',
+ cypher_key: 'enc_key'
+ )
+
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ order_id: '1',
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_request).returns(successful_authorize_response)
+
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12004172282310181802446007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_authorize
+ @gateway.expects(:ssl_request).returns(failed_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_equal '27', response.error_code
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_request).returns(successful_capture_response)
+
+ assert response = @gateway.capture(@amount, 'reference', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12204172322310181826516007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_request).returns(failed_capture_response)
+ response = @gateway.capture(@amount, 'reference', @options)
+
+ assert_failure response
+ assert_equal '807', response.error_code
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_request).returns(successful_purchase_response)
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12004172192310181720006007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_request).returns(failed_purchase_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_equal '27', response.error_code
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_request).returns(successful_refund_response)
+
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12204172352310181847426007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_request).returns(failed_refund_response)
+
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_failure response
+ assert response.test?
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_request).returns(successful_void_response)
+
+ assert response = @gateway.void('12204172352310181847426007000#1#10', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '14204172402310181906166007000#1#10', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_request).returns(failed_void_response)
+
+ assert response = @gateway.void('reference', @options)
+ assert_failure response
+ assert response.test?
+ end
+
+ private
+
+ def successful_authorize_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQxNzIyODIzMTAxODE4MDI0NDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==",
+ "firma":"2271f18614f9e3bf1f1d0bde7c23d2d9b576087564fd6cb4474f14f5727eaff2",
+ "fecha":"231018180245479",
+ "idProceso":"106900640-9da0de26e0e81697f7629566b99a1b73"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_response
+ <<~RESPONSE
+ {
+ "fecha":"231018180927186",
+ "idProceso":"106900640-9cfe017407164563ca5aa7a0877d2ade",
+ "codResult":"27"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIyMDQxNzIzMjIzMTAxODE4MjY1MTYwMDcwMDAiLCJjb2RBdXQiOiI5MDAifQ==",
+ "firma":"9dead8ef2bf1f82cde1954cefaa9eca67b630effed7f71a5fd3bb3bd2e6e0808",
+ "fecha":"231018182651711",
+ "idProceso":"106900640-5b03c604fd76ecaf8715a29c482f3040"
+ }
+ RESPONSE
+ end
+
+ def failed_capture_response
+ <<~RESPONSE
+ {
+ "fecha":"231018183020560",
+ "idProceso":"106900640-d0cab45d2404960b65fe02445e97b7e2",
+ "codResult":"807"
+ }
+ RESPONSE
+ end
+
+ def successful_purchase_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQxNzIxOTIzMTAxODE3MjAwMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==",
+ "firma":"da751ff809f54842ff26aed009cdce2d1a3b613cb3be579bb17af2e3ab36aa37",
+ "fecha":"231018172001775",
+ "idProceso":"106900640-bd4bd321774c51ec91cf24ca6bbca913"
+ }
+ RESPONSE
+ end
+
+ def failed_purchase_response
+ <<~RESPONSE
+ {
+ "fecha":"231018174516102",
+ "idProceso":"106900640-29c9d010e2e8c33872a4194df4e7a544",
+ "codResult":"27"
+ }
+ RESPONSE
+ end
+
+ def successful_refund_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIiwibnVtT3BlcmFjaW9uIjoiOGYyOTJiYTcwMmEzMTZmODIwMmEzZGFjY2JhMjFmZWMiLCJpbXBvcnRlIjoiMTAwIiwibnVtQXV0IjoiMTAxMDAwIiwicmVmZXJlbmNpYSI6IjEyMjA0MTcyMzUyMzEwMTgxODQ3NDI2MDA3MDAwIiwidGlwb09wZXJhY2lvbiI6IkQiLCJwYWlzIjoiMDAwIiwiY29kQXV0IjoiOTAwIn0=",
+ "firma":"37591482e4d1dce6317c6d7de6a6c9b030c0618680eaefb4b42b0d8af3854773",
+ "fecha":"231018184743876",
+ "idProceso":"106900640-8f292ba702a316f8202a3daccba21fec"
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<~RESPONSE
+ {
+ "fecha":"231018185809202",
+ "idProceso":"106900640-fc93d837dba2003ad767d682e6eb5d5f",
+ "codResult":"15"
+ }
+ RESPONSE
+ end
+
+ def successful_void_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIiwibnVtT3BlcmFjaW9uIjoiMDNlMTkwNTU4NWZlMmFjM2M4N2NiYjY4NGUyMjYwZDUiLCJpbXBvcnRlIjoiMTAwIiwibnVtQXV0IjoiMTAxMDAwIiwicmVmZXJlbmNpYSI6IjE0MjA0MTcyNDAyMzEwMTgxOTA2MTY2MDA3MDAwIiwidGlwb09wZXJhY2lvbiI6IkQiLCJwYWlzIjoiMDAwIiwiY29kQXV0IjoiNDAwIn0=",
+ "firma":"af55904b24cb083e6514b86456b107fdb8ebfc715aed228321ad959b13ef2b23",
+ "fecha":"231018190618224",
+ "idProceso":"106900640-03e1905585fe2ac3c87cbb684e2260d5"
+ }
+ RESPONSE
+ end
+
+ def failed_void_response
+ <<~RESPONSE
+ {
+ "fecha":"231018191116348",
+ "idProceso":"106900640-d7ca10f4fae36b2ad81f330eeb1ce509",
+ "codResult":"15"
+ }
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/cecabank_test.rb b/test/unit/gateways/cecabank_test.rb
index 2641b1b6800..e2bfe3c749e 100644
--- a/test/unit/gateways/cecabank_test.rb
+++ b/test/unit/gateways/cecabank_test.rb
@@ -4,11 +4,11 @@ class CecabankTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = CecabankGateway.new(
+ @gateway = CecabankXmlGateway.new(
merchant_id: '12345678',
acquirer_bin: '12345678',
terminal_id: '00000003',
- key: 'enc_key'
+ cypher_key: 'enc_key'
)
@credit_card = credit_card
From 9e87b8b8c22de97724e92622bea854c8f7110bf2 Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Fri, 3 Nov 2023 13:38:08 -0500
Subject: [PATCH 211/390] Cecabank: Add 3DS Global to Cecabank REST JSON
gateway (#4940)
Description
-------------------------
Include 3DS2 Global
For Spreedly reference:
SER-817
Unit test
-------------------------
Finished in 21.808339 seconds.
5660 tests, 78301 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
259.53 tests/s, 3590.42 assertions/s
Rubocop
-------------------------
Inspecting 778 files
778 files inspected, no offenses detected
Co-authored-by: Luis Urrea
---
CHANGELOG | 1 +
.../gateways/cecabank/cecabank_json.rb | 45 ++++++++++++++-----
.../remote_cecabank_rest_json_test.rb | 37 ++++-----------
3 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 5088e0c191a..2f0c1587b21 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -46,6 +46,7 @@
* Adyen: Add mcc field [jcreiff] #4926
* Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935
* Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920
+* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
index dc829c9e964..a621e93f8ce 100644
--- a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
@@ -24,6 +24,12 @@ class CecabankJsonGateway < Gateway
cardholder: :S
}.freeze
+ CECA_SCA_TYPES = {
+ low_value_exemption: :LOW,
+ transaction_risk_analysis_exemption: :TRA,
+ nil: :NONE
+ }.freeze
+
self.test_url = 'https://tpv.ceca.es/tpvweb/rest/procesos/'
self.live_url = 'https://pgw.ceca.es/tpvweb/rest/procesos/'
@@ -120,28 +126,45 @@ def add_creditcard(post, creditcard)
def add_stored_credentials(post, creditcard, options)
return unless stored_credential = options[:stored_credential]
- return if options[:exemption_sca] == 'NONE' || options[:exemption_sca].blank?
+ return if options[:exemption_type].blank? && !(stored_credential[:reason_type] && stored_credential[:initiator])
params = post[:parametros] ||= {}
- params[:exencionSCA] = options[:exemption_sca]
-
- if options[:exemption_sca] == 'MIT'
- requires!(stored_credential, :reason_type, :initiator)
+ params[:exencionSCA] = 'MIT'
+
+ requires!(stored_credential, :reason_type, :initiator)
+ reason_type = CECA_REASON_TYPES[stored_credential[:reason_type].to_sym]
+ initiator = CECA_INITIATOR[stored_credential[:initiator].to_sym]
+ params[:tipoCOF] = reason_type
+ params[:inicioRec] = initiator
+ if initiator == :S
requires!(options, :recurring_frequency)
+ params[:finRec] = options[:recurring_end_date] || strftime_yyyymm(creditcard)
+ params[:frecRec] = options[:recurring_frequency]
end
- params[:tipoCOF] = CECA_REASON_TYPES[stored_credential[:reason_type].to_sym]
- params[:inicioRec] = CECA_INITIATOR[stored_credential[:initiator].to_sym]
- params[:finRec] = options[:recurring_end_date] || strftime_yyyymm(creditcard)
- params[:frecRec] = options[:recurring_frequency]
-
params[:mmppTxId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
end
def add_three_d_secure(post, options)
params = post[:parametros] ||= {}
+ return unless three_d_secure = options[:three_d_secure]
+
+ params[:exencionSCA] ||= CECA_SCA_TYPES[options[:exemption_type]&.to_sym]
+ three_d_response = {
+ exemption_type: options[:exemption_type],
+ three_ds_version: three_d_secure[:version],
+ authentication_value: three_d_secure[:cavv],
+ directory_server_transaction_id: three_d_secure[:ds_transaction_id],
+ acs_transaction_id: three_d_secure[:acs_transaction_id],
+ authentication_response_status: three_d_secure[:authentication_response_status],
+ three_ds_server_trans_id: three_d_secure[:three_ds_server_trans_id],
+ ecommerce_indicator: three_d_secure[:eci],
+ enrolled: three_d_secure[:enrolled]
+ }
+
+ three_d_response.merge!({ amount: post[:parametros][:importe] })
- params[:ThreeDsResponse] = options[:three_d_secure] if options[:three_d_secure]
+ params[:ThreeDsResponse] = three_d_response.to_json
end
def commit(action, post, method = :post)
diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb
index 582560215ad..22249741485 100644
--- a/test/remote/gateways/remote_cecabank_rest_json_test.rb
+++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb
@@ -10,12 +10,10 @@ def setup
@options = {
order_id: generate_unique_id,
- exemption_sca: 'NONE',
three_d_secure: three_d_secure
}
@cit_options = @options.merge({
- exemption_sca: 'MIT',
recurring_end_date: '20231231',
recurring_frequency: '1',
stored_credential: {
@@ -125,7 +123,7 @@ def test_purchase_using_stored_credential_recurring_mit
assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
assert_success purchase
- options = @cit_options
+ options = @cit_options.except(:three_d_secure, :extra_options_for_three_d_secure)
options[:stored_credential][:reason_type] = 'recurring'
options[:stored_credential][:initiator] = 'merchant'
options[:stored_credential][:network_transaction_id] = purchase.network_transaction_id
@@ -150,32 +148,13 @@ def test_failure_stored_credential_invalid_cit_transaction_id
def three_d_secure
{
- authentication_value: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
- email: 'example@example.com',
- three_ds_version: '2.2.0',
- directory_server_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
- exemption_type: 'null',
- token: 'Gt3wYk5kEaxCON6byEMWYsW58iq',
- xid: 'xx',
- authentication_response_status: 'Y',
- ecommerce_indicator: '02',
- sca_provider_key: 'W2Y0DOpdImu6AjaWYPituPWkh2C',
- gateway_transaction_key: 'A7YADMeCYYjG4d9ozKUQiQvpGZp',
- transaction_type: 'Sca::Authentication',
- daf: 'false',
- challenge_form: '',
+ version: '2.2.0',
+ eci: '02',
+ cavv: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
+ ds_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
acs_transaction_id: '18c353b0-76e3-4a4c-8033-f14fe9ce39dc',
- updated_at: '2023-08-29T08:03:40Z',
- three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5',
- enrolled: 'true',
- flow_performed: 'challenge',
- succeeded: true,
- amount: 11000,
- ip: '119.119.35.111',
- directory_response_status: 'C',
- currency_code: 'ISK',
- created_at: '2023-08-29T08:02:42Z',
- state: 'succeeded'
- }.to_json.to_s
+ authentication_response_status: 'Y',
+ three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5'
+ }
end
end
From bcc5e166bb9993ddca5e3951fe684a19046e16ce Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Mon, 6 Nov 2023 10:12:47 -0500
Subject: [PATCH 212/390] Cecabank: Add scrub implementation (#4945)
Description
-------------------------
Add scrub logic to protect sensitive data from the credit card
For Spreedly reference:
SER-956
Unit test
-------------------------
Finished in 25.362182 seconds.
5664 tests, 78328 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
223.32 tests/s, 3088.38 assertions/s
Rubocop
-------------------------
Inspecting 778 files
778 files inspected, no offenses detected
Co-authored-by: Luis Urrea
---
CHANGELOG | 1 +
.../gateways/cecabank/cecabank_common.rb | 4 +++
.../gateways/cecabank/cecabank_json.rb | 25 ++++++++++++++++---
.../billing/gateways/cecabank/cecabank_xml.rb | 4 ---
.../remote_cecabank_rest_json_test.rb | 10 ++++++++
test/unit/gateways/cecabank_rest_json_test.rb | 12 +++++++++
6 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 2f0c1587b21..c1087174eb5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -47,6 +47,7 @@
* Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935
* Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920
* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
+* Cecabank: Add scrub implementation [sinourain] #4945
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
index d71268649d6..a397c2955c8 100644
--- a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
@@ -29,4 +29,8 @@ def initialize(options = {})
requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :cypher_key)
super
end
+
+ def supports_scrubbing?
+ true
+ end
end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
index a621e93f8ce..86c27e3db58 100644
--- a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
@@ -63,6 +63,21 @@ def refund(money, identification, options = {})
handle_cancellation(:refund, money, authorization, options)
end
+ def scrub(transcript)
+ before_message = transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"')
+ request_data = JSON.parse(before_message)
+ params = decode_params(request_data['parametros']).
+ gsub(%r(("pan\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("caducidad\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv2\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("csc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
+ request_data['parametros'] = encode_params(params)
+
+ before_message = before_message.gsub(%r(\")i, '\\\"')
+ after_message = request_data.to_json.gsub(%r(\")i, '\\\"')
+ transcript.sub(before_message, after_message)
+ end
+
private
def handle_purchase(action, money, creditcard, options)
@@ -176,7 +191,7 @@ def commit(action, post, method = :post)
add_encryption(post)
add_merchant_data(post)
- params_encoded = encode_params(post)
+ params_encoded = encode_post_parameters(post)
add_signature(post, params_encoded, options)
response = parse(ssl_request(method, url(action), post.to_json, headers))
@@ -214,8 +229,12 @@ def parse(string)
parse(decode_params(string))
end
- def encode_params(post)
- post[:parametros] = Base64.strict_encode64(post[:parametros].to_json)
+ def encode_post_parameters(post)
+ post[:parametros] = encode_params(post[:parametros].to_json)
+ end
+
+ def encode_params(params)
+ Base64.strict_encode64(params)
end
def decode_params(params)
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
index ba9f727d98f..d670e23ab49 100644
--- a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
@@ -71,10 +71,6 @@ def refund(money, identification, options = {})
commit(CECA_ACTION_REFUND, post)
end
- def supports_scrubbing?
- true
- end
-
def scrub(transcript)
transcript.
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb
index 22249741485..3abd1878553 100644
--- a/test/remote/gateways/remote_cecabank_rest_json_test.rb
+++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb
@@ -144,6 +144,16 @@ def test_failure_stored_credential_invalid_cit_transaction_id
assert_match '810', purchase.error_code
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
+
private
def three_d_secure
diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb
index 98b34b5963b..87ba73ef771 100644
--- a/test/unit/gateways/cecabank_rest_json_test.rb
+++ b/test/unit/gateways/cecabank_rest_json_test.rb
@@ -110,8 +110,20 @@ def test_failed_void
assert response.test?
end
+ def test_transcript_scrubbing
+ assert_equal scrubbed_transcript, @gateway.scrub(transcript)
+ end
+
private
+ def transcript
+ "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nContent-Length: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiNDUwNzY3MDAwMTAwMDAwOSIsImNhZHVjaWRhZCI6IjIwMjMxMiIsImN2djIiOiI5ODkiLCJleGVuY2lvblNDQSI6bnVsbCwiVGhyZWVEc1Jlc3BvbnNlIjoie1wiZXhlbXB0aW9uX3R5cGVcIjpudWxsLFwidGhyZWVfZHNfdmVyc2lvblwiOlwiMi4yLjBcIixcImF1dGhlbnRpY2F0aW9uX3ZhbHVlXCI6XCI0RjgwREY1MEFEQjBGOTUwMkI5MTYxOEU5QjcwNDc5MEVBQkEzNUZERkM5NzJEREREMEJGNDk4QzZBNzVFNDkyXCIsXCJkaXJlY3Rvcnlfc2VydmVyX3RyYW5zYWN0aW9uX2lkXCI6XCJhMmJmMDg5Zi1jZWZjLTRkMmMtODUwZi05MTUzODI3ZmUwNzBcIixcImFjc190cmFuc2FjdGlvbl9pZFwiOlwiMThjMzUzYjAtNzZlMy00YTRjLTgwMzMtZjE0ZmU5Y2UzOWRjXCIsXCJhdXRoZW50aWNhdGlvbl9yZXNwb25zZV9zdGF0dXNcIjpcIllcIixcInRocmVlX2RzX3NlcnZlcl90cmFuc19pZFwiOlwiOWJkOWFhOWMtM2JlYi00MDEyLThlNTItMjE0Y2NjYjI1ZWM1XCIsXCJlY29tbWVyY2VfaW5kaWNhdG9yXCI6XCIwMlwiLFwiZW5yb2xsZWRcIjpudWxsLFwiYW1vdW50XCI6XCIxMDBcIn0iLCJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIn0=\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n"
+ end
+
+ def scrubbed_transcript
+ "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nContent-Length: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiW0ZJTFRFUkVEXSIsImNhZHVjaWRhZCI6IltGSUxURVJFRF0iLCJjdnYyIjoiW0ZJTFRFUkVEXSIsImV4ZW5jaW9uU0NBIjpudWxsLCJUaHJlZURzUmVzcG9uc2UiOiJ7XCJleGVtcHRpb25fdHlwZVwiOm51bGwsXCJ0aHJlZV9kc192ZXJzaW9uXCI6XCIyLjIuMFwiLFwiYXV0aGVudGljYXRpb25fdmFsdWVcIjpcIjRGODBERjUwQURCMEY5NTAyQjkxNjE4RTlCNzA0NzkwRUFCQTM1RkRGQzk3MkREREQwQkY0OThDNkE3NUU0OTJcIixcImRpcmVjdG9yeV9zZXJ2ZXJfdHJhbnNhY3Rpb25faWRcIjpcImEyYmYwODlmLWNlZmMtNGQyYy04NTBmLTkxNTM4MjdmZTA3MFwiLFwiYWNzX3RyYW5zYWN0aW9uX2lkXCI6XCIxOGMzNTNiMC03NmUzLTRhNGMtODAzMy1mMTRmZTljZTM5ZGNcIixcImF1dGhlbnRpY2F0aW9uX3Jlc3BvbnNlX3N0YXR1c1wiOlwiWVwiLFwidGhyZWVfZHNfc2VydmVyX3RyYW5zX2lkXCI6XCI5YmQ5YWE5Yy0zYmViLTQwMTItOGU1Mi0yMTRjY2NiMjVlYzVcIixcImVjb21tZXJjZV9pbmRpY2F0b3JcIjpcIjAyXCIsXCJlbnJvbGxlZFwiOm51bGwsXCJhbW91bnRcIjpcIjEwMFwifSIsIm1lcmNoYW50SUQiOiIxMDY5MDA2NDAiLCJhY3F1aXJlckJJTiI6IjAwMDA1NTQwMDAiLCJ0ZXJtaW5hbElEIjoiMDAwMDAwMDMifQ==\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n"
+ end
+
def successful_authorize_response
<<~RESPONSE
{
From 330f0b94832921648462b05d71d350e8e60152c8 Mon Sep 17 00:00:00 2001
From: Dustin A Haefele <45601251+DustinHaefele@users.noreply.github.com>
Date: Tue, 7 Nov 2023 10:42:12 -0500
Subject: [PATCH 213/390] GlobalCollect: Fix bug in success_from logic (#4939)
Some applepay transactions that were through global collect direct (ogone)
were being marked as failed despite success. The response from direct was
slightly different than the standard global collect so the logic wasn't seeing
them as successful.
Spreedly reference ticket: ECS-3197
remote tests:
55 tests, 144 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
0.78 tests/s, 2.05 assertions/s
unit tests:
45 tests, 230 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
1497.80 tests/s, 7655.44 assertions/s
---
CHANGELOG | 1 +
.../billing/gateways/global_collect.rb | 2 --
.../gateways/remote_global_collect_test.rb | 26 +++++++++++++++++++
3 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c1087174eb5..d26bc357063 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -48,6 +48,7 @@
* Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920
* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
* Cecabank: Add scrub implementation [sinourain] #4945
+* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index 240f7179e1d..8e66a82c742 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -522,8 +522,6 @@ def content_type
def success_from(action, response)
return false if response['errorId'] || response['error_message']
- return %w(CAPTURED CAPTURE_REQUESTED).include?(response.dig('payment', 'status')) if response.dig('payment', 'paymentOutput', 'paymentMethod') == 'mobile'
-
case action
when :authorize
response.dig('payment', 'statusOutput', 'isAuthorized')
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index 8fa0b9c091d..f8657744af1 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -105,6 +105,32 @@ def test_successful_purchase_with_apple_pay
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
+ def test_successful_authorize_with_apple_pay
+ options = @preprod_options.merge(requires_approval: false, currency: 'USD')
+ response = @gateway_preprod.authorize(4500, @apple_pay, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
+ end
+
+ def test_successful_purchase_with_apple_pay_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'USD')
+ response = @gateway_direct.purchase(100, @apple_pay, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'PENDING_CAPTURE', response.params['payment']['status']
+ end
+
+ def test_successful_authorize_and_capture_with_apple_pay_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'USD')
+ auth = @gateway_direct.authorize(100, @apple_pay, options)
+ assert_success auth
+
+ assert capture = @gateway_direct.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
def test_successful_purchase_with_google_pay
options = @preprod_options.merge(requires_approval: false)
response = @gateway_preprod.purchase(4500, @google_pay, options)
From 3e1cd717b203e3d63eba9e42c7caeb631866c241 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Wed, 1 Nov 2023 12:15:16 -0500
Subject: [PATCH 214/390] SafeCharge: Support tokens
Remote:
34 tests, 96 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
29 tests, 160 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/safe_charge.rb | 53 +++++++++++++------
.../gateways/remote_safe_charge_test.rb | 10 ++++
test/unit/gateways/safe_charge_test.rb | 21 +++++---
4 files changed, 62 insertions(+), 23 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d26bc357063..f24e6cc9c07 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -49,6 +49,7 @@
* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
* Cecabank: Add scrub implementation [sinourain] #4945
* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
+* SafeCharge: Support tokens [almalee24] #4941
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index a4be0c6fcd1..4c3fb490d00 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -149,26 +149,46 @@ def add_transaction_data(trans_type, post, money, options)
end
def add_payment(post, payment, options = {})
- post[:sg_ExpMonth] = format(payment.month, :two_digits)
- post[:sg_ExpYear] = format(payment.year, :two_digits)
- post[:sg_CardNumber] = payment.number
-
- if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
- post[:sg_CAVV] = payment.payment_cryptogram
- post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
- post[:sg_IsExternalMPI] = 1
- post[:sg_ExternalTokenProvider] = 5
- else
- post[:sg_CVV2] = payment.verification_value
- post[:sg_NameOnCard] = payment.name
- post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ case payment
+ when String
+ add_token(post, payment)
+ when CreditCard
+ post[:sg_ExpMonth] = format(payment.month, :two_digits)
+ post[:sg_ExpYear] = format(payment.year, :two_digits)
+ post[:sg_CardNumber] = payment.number
+
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
+ add_network_token(post, payment, options)
+ else
+ add_credit_card(post, payment, options)
+ end
end
end
+ def add_token(post, payment)
+ _, transaction_id, _, _, _, _, _, token = payment.split('|')
+
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ end
+
+ def add_credit_card(post, payment, options)
+ post[:sg_CVV2] = payment.verification_value
+ post[:sg_NameOnCard] = payment.name
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ end
+
+ def add_network_token(post, payment, options)
+ post[:sg_CAVV] = payment.payment_cryptogram
+ post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
+ post[:sg_IsExternalMPI] = 1
+ post[:sg_ExternalTokenProvider] = 5
+ end
+
def add_customer_details(post, payment, options)
if address = options[:billing_address] || options[:address]
- post[:sg_FirstName] = payment.first_name
- post[:sg_LastName] = payment.last_name
+ post[:sg_FirstName] = payment.first_name if payment.respond_to?(:first_name)
+ post[:sg_LastName] = payment.last_name if payment.respond_to?(:last_name)
post[:sg_Address] = address[:address1] if address[:address1]
post[:sg_City] = address[:city] if address[:city]
post[:sg_State] = address[:state] if address[:state]
@@ -256,7 +276,8 @@ def authorization_from(response, parameters)
parameters[:sg_ExpMonth],
parameters[:sg_ExpYear],
parameters[:sg_Amount],
- parameters[:sg_Currency]
+ parameters[:sg_Currency],
+ response[:token]
].join('|')
end
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 8e8fe4dd945..5c9d81fc6b6 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -63,6 +63,16 @@ def test_successful_purchase
assert_equal 'Success', response.message
end
+ def test_successful_purchase_with_token
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ subsequent_response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success subsequent_response
+ assert_equal 'Success', subsequent_response.message
+ end
+
def test_successful_purchase_with_non_fractional_currency
options = @options.merge(currency: 'CLP')
response = @gateway.purchase(127999, @credit_card, options)
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index 23a579e569f..b9ef695fd6c 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -60,7 +60,8 @@ def test_successful_purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|' \
+ 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
assert response.test?
end
@@ -76,7 +77,8 @@ def test_successful_purchase_with_merchant_options
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|' \
+ 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -90,7 +92,8 @@ def test_successful_purchase_with_truthy_stored_credential_mode
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|'\
+ 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -104,7 +107,8 @@ def test_successful_purchase_with_falsey_stored_credential_mode
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' \
+ '|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -124,7 +128,8 @@ def test_successful_authorize
assert_equal '111534|101508189855|MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAW' \
'wBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAE' \
- 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
+ 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD|' \
+ 'MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAWwBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAEwAUAA1AFUAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
assert response.test?
end
@@ -173,7 +178,8 @@ def test_successful_capture
assert_equal '111301|101508190200|RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZ' \
'AAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAF' \
- 'EAYgBVAHIAMwA=|month|year|1.00|currency', response.authorization
+ 'EAYgBVAHIAMwA=|month|year|1.00|currency|' \
+ 'RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZAAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAFEAYgBVAHIAMwA=', response.authorization
assert response.test?
end
@@ -285,7 +291,8 @@ def test_successful_void
assert_equal '111171|101508208625|ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAV' \
'QBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AG' \
- 'wAYwBUAE0AMwA=|month|year|0.00|currency', response.authorization
+ 'wAYwBUAE0AMwA=|month|year|0.00|currency|' \
+ 'ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAVQBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AGwAYwBUAE0AMwA=', response.authorization
assert response.test?
end
From 04754c66ffe54789f0c7b16b274b24e2066db150 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 7 Nov 2023 12:21:09 -0600
Subject: [PATCH 215/390] Revert "SafeCharge: Support tokens"
This reverts commit 3e1cd717b203e3d63eba9e42c7caeb631866c241.
---
CHANGELOG | 1 -
.../billing/gateways/safe_charge.rb | 53 ++++++-------------
.../gateways/remote_safe_charge_test.rb | 10 ----
test/unit/gateways/safe_charge_test.rb | 21 +++-----
4 files changed, 23 insertions(+), 62 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f24e6cc9c07..d26bc357063 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -49,7 +49,6 @@
* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
* Cecabank: Add scrub implementation [sinourain] #4945
* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
-* SafeCharge: Support tokens [almalee24] #4941
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index 4c3fb490d00..a4be0c6fcd1 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -149,46 +149,26 @@ def add_transaction_data(trans_type, post, money, options)
end
def add_payment(post, payment, options = {})
- case payment
- when String
- add_token(post, payment)
- when CreditCard
- post[:sg_ExpMonth] = format(payment.month, :two_digits)
- post[:sg_ExpYear] = format(payment.year, :two_digits)
- post[:sg_CardNumber] = payment.number
-
- if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
- add_network_token(post, payment, options)
- else
- add_credit_card(post, payment, options)
- end
+ post[:sg_ExpMonth] = format(payment.month, :two_digits)
+ post[:sg_ExpYear] = format(payment.year, :two_digits)
+ post[:sg_CardNumber] = payment.number
+
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
+ post[:sg_CAVV] = payment.payment_cryptogram
+ post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
+ post[:sg_IsExternalMPI] = 1
+ post[:sg_ExternalTokenProvider] = 5
+ else
+ post[:sg_CVV2] = payment.verification_value
+ post[:sg_NameOnCard] = payment.name
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
end
end
- def add_token(post, payment)
- _, transaction_id, _, _, _, _, _, token = payment.split('|')
-
- post[:sg_TransactionID] = transaction_id
- post[:sg_CCToken] = token
- end
-
- def add_credit_card(post, payment, options)
- post[:sg_CVV2] = payment.verification_value
- post[:sg_NameOnCard] = payment.name
- post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
- end
-
- def add_network_token(post, payment, options)
- post[:sg_CAVV] = payment.payment_cryptogram
- post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
- post[:sg_IsExternalMPI] = 1
- post[:sg_ExternalTokenProvider] = 5
- end
-
def add_customer_details(post, payment, options)
if address = options[:billing_address] || options[:address]
- post[:sg_FirstName] = payment.first_name if payment.respond_to?(:first_name)
- post[:sg_LastName] = payment.last_name if payment.respond_to?(:last_name)
+ post[:sg_FirstName] = payment.first_name
+ post[:sg_LastName] = payment.last_name
post[:sg_Address] = address[:address1] if address[:address1]
post[:sg_City] = address[:city] if address[:city]
post[:sg_State] = address[:state] if address[:state]
@@ -276,8 +256,7 @@ def authorization_from(response, parameters)
parameters[:sg_ExpMonth],
parameters[:sg_ExpYear],
parameters[:sg_Amount],
- parameters[:sg_Currency],
- response[:token]
+ parameters[:sg_Currency]
].join('|')
end
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 5c9d81fc6b6..8e8fe4dd945 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -63,16 +63,6 @@ def test_successful_purchase
assert_equal 'Success', response.message
end
- def test_successful_purchase_with_token
- response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'Success', response.message
-
- subsequent_response = @gateway.purchase(@amount, response.authorization, @options)
- assert_success subsequent_response
- assert_equal 'Success', subsequent_response.message
- end
-
def test_successful_purchase_with_non_fractional_currency
options = @options.merge(currency: 'CLP')
response = @gateway.purchase(127999, @credit_card, options)
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index b9ef695fd6c..23a579e569f 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -60,8 +60,7 @@ def test_successful_purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|' \
- 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
assert response.test?
end
@@ -77,8 +76,7 @@ def test_successful_purchase_with_merchant_options
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|' \
- 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -92,8 +90,7 @@ def test_successful_purchase_with_truthy_stored_credential_mode
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD|'\
- 'ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -107,8 +104,7 @@ def test_successful_purchase_with_falsey_stored_credential_mode
assert_success purchase
assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \
'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \
- 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' \
- '|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
+ 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization
assert purchase.test?
end
@@ -128,8 +124,7 @@ def test_successful_authorize
assert_equal '111534|101508189855|MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAW' \
'wBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAE' \
- 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD|' \
- 'MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAWwBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAEwAUAA1AFUAMwA=' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
+ 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization
assert response.test?
end
@@ -178,8 +173,7 @@ def test_successful_capture
assert_equal '111301|101508190200|RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZ' \
'AAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAF' \
- 'EAYgBVAHIAMwA=|month|year|1.00|currency|' \
- 'RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZAAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAFEAYgBVAHIAMwA=', response.authorization
+ 'EAYgBVAHIAMwA=|month|year|1.00|currency', response.authorization
assert response.test?
end
@@ -291,8 +285,7 @@ def test_successful_void
assert_equal '111171|101508208625|ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAV' \
'QBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AG' \
- 'wAYwBUAE0AMwA=|month|year|0.00|currency|' \
- 'ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAVQBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AGwAYwBUAE0AMwA=', response.authorization
+ 'wAYwBUAE0AMwA=|month|year|0.00|currency', response.authorization
assert response.test?
end
From 66b3a6dbb9c10031e134c571ed1a04ef951875c5 Mon Sep 17 00:00:00 2001
From: Dustin A Haefele <45601251+DustinHaefele@users.noreply.github.com>
Date: Tue, 7 Nov 2023 13:43:03 -0500
Subject: [PATCH 216/390] Worldpay: Update 3ds logic to accept df_reference_id
directly (#4929)
unit tests:
113 tests, 645 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
426.14 tests/s, 2432.37 assertions/s
remote tests:
100 tests, 412 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
ticket for spreedly tracking: ECS-3125
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/worldpay.rb | 2 +-
test/remote/gateways/remote_worldpay_test.rb | 14 +++++---------
test/unit/gateways/worldpay_test.rb | 4 +++-
4 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d26bc357063..0b2c014c17e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -49,6 +49,7 @@
* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
* Cecabank: Add scrub implementation [sinourain] #4945
* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
+* Worldpay: Update 3ds logic to accept df_reference_id directly [DustinHaefele] #4929
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index 30e93ce882d..b6ba7faf243 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -443,7 +443,7 @@ def add_token_for_ff_credit(xml, payment_method, options)
end
def add_additional_3ds_data(xml, options)
- additional_data = { 'dfReferenceId' => options[:session_id] }
+ additional_data = { 'dfReferenceId' => options[:df_reference_id] }
additional_data['challengeWindowSize'] = options[:browser_size] if options[:browser_size]
xml.additional3DSData additional_data
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index 585c554b700..cf90dfe3e28 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -455,9 +455,8 @@ def test_successful_authorize_with_3ds
)
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
+ assert first_message.success?
refute first_message.authorization.blank?
- refute first_message.params['issuer_url'].blank?
- refute first_message.params['pa_request'].blank?
refute first_message.params['cookie'].blank?
refute first_message.params['session_id'].blank?
end
@@ -483,8 +482,7 @@ def test_successful_authorize_with_3ds2_challenge
assert response = @gateway.authorize(@amount, @threeDS2_challenge_card, options)
assert response.test?
refute response.authorization.blank?
- refute response.params['issuer_url'].blank?
- refute response.params['pa_request'].blank?
+ assert response.success?
refute response.params['cookie'].blank?
refute response.params['session_id'].blank?
end
@@ -570,8 +568,7 @@ def test_successful_authorize_with_3ds_with_normalized_stored_credentials
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
refute first_message.authorization.blank?
- refute first_message.params['issuer_url'].blank?
- refute first_message.params['pa_request'].blank?
+ assert first_message.success?
refute first_message.params['cookie'].blank?
refute first_message.params['session_id'].blank?
end
@@ -592,8 +589,7 @@ def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
refute first_message.authorization.blank?
- refute first_message.params['issuer_url'].blank?
- refute first_message.params['pa_request'].blank?
+ assert first_message.success?
refute first_message.params['cookie'].blank?
refute first_message.params['session_id'].blank?
end
@@ -869,7 +865,7 @@ def test_successful_fast_fund_credit_with_token_on_cft_gateway
def test_failed_fast_fund_credit_on_cft_gateway
options = @options.merge({ fast_fund_credit: true })
- refused_card = credit_card('4917300800000000', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
+ refused_card = credit_card('4444333322221111', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
credit = @cftgateway.credit(@amount, refused_card, options)
assert_failure credit
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index a256785be9d..fddad02e088 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -1097,9 +1097,11 @@ def test_3ds_name_not_coerced_in_production
def test_3ds_additional_information
browser_size = '390x400'
session_id = '0215ui8ib1'
+ df_reference_id = '1326vj9jc2'
options = @options.merge(
session_id: session_id,
+ df_reference_id: df_reference_id,
browser_size: browser_size,
execute_threed: true,
three_ds_version: '2.0.1'
@@ -1108,7 +1110,7 @@ def test_3ds_additional_information
stub_comms do
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
- assert_tag_with_attributes 'additional3DSData', { 'dfReferenceId' => session_id, 'challengeWindowSize' => browser_size }, data
+ assert_tag_with_attributes 'additional3DSData', { 'dfReferenceId' => df_reference_id, 'challengeWindowSize' => browser_size }, data
end.respond_with(successful_authorize_response)
end
From f260f0af53d6251bb202c4934b9fbfb0af2beaa2 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Tue, 7 Nov 2023 16:30:37 -0500
Subject: [PATCH 217/390] Orbital: Enable Third Party Vaulting (#4928)
Description
-------------------------
[SER-782](https://spreedly.atlassian.net/browse/SER-782)
This commit enable third party vaulting for Orbital
in this case Orbital needs a flag to enable the store process
and it doesn't support unstore function
Unit test
-------------------------
Finished in 1.849371 seconds.
147 tests, 831 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
79.49 tests/s, 449.34 assertions/s
Remote test
-------------------------
Finished in 101.551157 seconds.
126 tests, 509 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
92.8571% passed
1.24 tests/s, 5.01 assertions/s
Rubocop
-------------------------
773 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
.../billing/gateways/orbital.rb | 30 ++++--
test/fixtures.yml | 5 +
test/remote/gateways/remote_orbital_test.rb | 95 ++++++++++++++++++-
test/unit/gateways/orbital_test.rb | 44 +++++++++
5 files changed, 168 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 0b2c014c17e..bc1a3f5e704 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -50,6 +50,7 @@
* Cecabank: Add scrub implementation [sinourain] #4945
* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
* Worldpay: Update 3ds logic to accept df_reference_id directly [DustinHaefele] #4929
+* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb
index 5963b32e9fa..5af61e29213 100644
--- a/lib/active_merchant/billing/gateways/orbital.rb
+++ b/lib/active_merchant/billing/gateways/orbital.rb
@@ -193,6 +193,10 @@ class OrbitalGateway < Gateway
'checking' => 'C'
}
+ # safetech token flags
+ GET_TOKEN = 'GT'
+ USE_TOKEN = 'UT'
+
def initialize(options = {})
requires!(options, :merchant_id)
requires!(options, :login, :password) unless options[:ip_authentication]
@@ -253,6 +257,11 @@ def credit(money, payment_method, options = {})
commit(order, :refund, options[:retry_logic], options[:trace_number])
end
+ # Orbital save a payment method if the TokenTxnType is 'GT', that's why we use this as the default value for store
+ def store(creditcard, options = {})
+ authorize(0, creditcard, options.merge({ token_txn_type: GET_TOKEN }))
+ end
+
def void(authorization, options = {}, deprecated = {})
if !options.kind_of?(Hash)
ActiveMerchant.deprecated('Calling the void method with an amount parameter is deprecated and will be removed in a future version.')
@@ -536,9 +545,9 @@ def add_customer_address(xml, options)
end
def billing_name(payment_source, options)
- if payment_source&.name.present?
+ if !payment_source.is_a?(String) && payment_source&.name.present?
payment_source.name[0..29]
- elsif options[:billing_address][:name].present?
+ elsif options[:billing_address] && options[:billing_address][:name].present?
options[:billing_address][:name][0..29]
end
end
@@ -555,10 +564,16 @@ def get_address(options)
options[:billing_address] || options[:address]
end
+ def add_safetech_token_data(xml, payment_source, options)
+ xml.tag! :CardBrand, options[:card_brand]
+ xml.tag! :AccountNum, payment_source
+ end
+
#=====PAYMENT SOURCE FIELDS=====
# Payment can be done through either Credit Card or Electronic Check
def add_payment_source(xml, payment_source, options = {})
+ add_safetech_token_data(xml, payment_source, options) if options[:token_txn_type] == USE_TOKEN
payment_source.is_a?(Check) ? add_echeck(xml, payment_source, options) : add_credit_card(xml, payment_source, options)
end
@@ -576,10 +591,10 @@ def add_echeck(xml, check, options = {})
end
def add_credit_card(xml, credit_card, options)
- xml.tag! :AccountNum, credit_card.number if credit_card
- xml.tag! :Exp, expiry_date(credit_card) if credit_card
+ xml.tag! :AccountNum, credit_card.number if credit_card.is_a?(CreditCard)
+ xml.tag! :Exp, expiry_date(credit_card) if credit_card.is_a?(CreditCard)
add_currency_fields(xml, options[:currency])
- add_verification_value(xml, credit_card) if credit_card
+ add_verification_value(xml, credit_card) if credit_card.is_a?(CreditCard)
end
def add_verification_value(xml, credit_card)
@@ -886,12 +901,14 @@ def commit(order, message_type, retry_logic = nil, trace_number = nil)
request.call(remote_url(:secondary))
end
+ authorization = order.include?('GT') ? response[:safetech_token] : authorization_string(response[:tx_ref_num], response[:order_id])
+
Response.new(
success?(response, message_type),
message_from(response),
response,
{
- authorization: authorization_string(response[:tx_ref_num], response[:order_id]),
+ authorization: authorization,
test: self.test?,
avs_result: OrbitalGateway::AVSResult.new(response[:avs_resp_code]),
cvv_result: OrbitalGateway::CVVResult.new(response[:cvv2_resp_code])
@@ -1014,6 +1031,7 @@ def build_new_order_xml(action, money, payment_source, parameters = {})
add_stored_credentials(xml, parameters)
add_pymt_brand_program_code(xml, payment_source, three_d_secure)
add_mastercard_fields(xml, payment_source, parameters, three_d_secure) if mastercard?(payment_source)
+ xml.tag! :TokenTxnType, parameters[:token_txn_type] if parameters[:token_txn_type]
end
end
xml.target!
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 2acfa7b16cc..7f3606fbc68 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -727,6 +727,11 @@ orbital_tandem_gateway:
password: PASSWORD
merchant_id: MERCHANTID
+orbital_tpv_gateway:
+ login: LOGIN
+ password: PASSWORD
+ merchant_id: MERCHANTID
+
# Working credentials, no need to replace
pagarme:
api_key: 'ak_test_e1QGU2gL98MDCHZxHLJ9sofPUFJ7tH'
diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb
index 398e33b53c4..6ae0530fd6d 100644
--- a/test/remote/gateways/remote_orbital_test.rb
+++ b/test/remote/gateways/remote_orbital_test.rb
@@ -6,10 +6,11 @@ def setup
@gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_gateway))
@echeck_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_asv_aoa_gateway))
@three_ds_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_3ds_gateway))
-
+ @tpv_orbital_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_tpv_gateway))
@amount = 100
@google_pay_amount = 10000
@credit_card = credit_card('4556761029983886')
+ @mastercard_card_tpv = credit_card('2521000000000006')
@declined_card = credit_card('4000300011112220')
# Electronic Check object with test credentials of saving account
@echeck = check(account_number: '072403004', account_type: 'savings', routing_number: '072403004')
@@ -829,6 +830,98 @@ def test_successful_verify
assert_equal 'No reason to decline', response.message
end
+ def test_successful_store
+ response = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success response
+ assert_false response.params['safetech_token'].blank?
+ end
+
+ def test_successful_purchase_stored_token
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ # The 'UT' value means use token, To tell Orbital we want to use the stored payment method
+ # The 'VI' value means an abbreviation for the card brand 'VISA'.
+ response = @tpv_orbital_gateway.purchase(@amount, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT'))
+ assert_success response
+ assert_equal response.params['card_brand'], 'VI'
+ end
+
+ def test_successful_authorize_stored_token
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(29, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT'))
+ assert_success auth
+ end
+
+ def test_successful_authorize_stored_token_mastercard
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ response = @tpv_orbital_gateway.authorize(29, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT'))
+ assert_success response
+ assert_equal response.params['card_brand'], 'VI'
+ end
+
+ def test_failed_authorize_and_capture
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ response = @tpv_orbital_gateway.capture(39, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT'))
+ assert_failure response
+ assert_equal response.params['status_msg'], "The LIDM you supplied (#{store.authorization}) does not match with any existing transaction"
+ end
+
+ def test_successful_authorize_and_capture_with_stored_token
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(28, store.authorization, @options.merge(card_brand: 'MC', token_txn_type: 'UT'))
+ assert_success auth
+ assert_equal auth.params['card_brand'], 'MC'
+ response = @tpv_orbital_gateway.capture(28, auth.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_authorize_with_stored_token_and_refund
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(38, store.authorization, @options.merge(card_brand: 'MC', token_txn_type: 'UT'))
+ assert_success auth
+ response = @tpv_orbital_gateway.refund(38, auth.authorization, @options)
+ assert_success response
+ end
+
+ def test_failed_refund_wrong_token
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(38, store.authorization, @options.merge(card_brand: 'MC', token_txn_type: 'UT'))
+ assert_success auth
+ response = @tpv_orbital_gateway.refund(38, store.authorization, @options)
+ assert_failure response
+ assert_equal response.params['status_msg'], "The LIDM you supplied (#{store.authorization}) does not match with any existing transaction"
+ end
+
+ def test_successful_purchase_with_stored_token_and_refund
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ purchase = @tpv_orbital_gateway.purchase(38, store.authorization, @options.merge(card_brand: 'MC', token_txn_type: 'UT'))
+ assert_success purchase
+ response = @tpv_orbital_gateway.refund(38, purchase.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_without_store
+ response = @tpv_orbital_gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.params['safetech_token'], nil
+ end
+
+ def test_failed_purchase_with_stored_token
+ auth = @tpv_orbital_gateway.authorize(@amount, @credit_card, @options.merge(store: true))
+ assert_success auth
+ options = @options.merge!(card_brand: 'VI')
+ response = @tpv_orbital_gateway.purchase(@amount, nil, options)
+ assert_failure response
+ assert_equal response.params['status_msg'], 'Error. The Orbital Gateway has received a badly formatted message. The account number is required for this transaction'
+ end
+
def test_successful_different_cards
@credit_card.brand = 'master'
response = @gateway.verify(@credit_card, @options)
diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb
index fad86ef4c85..16487eb36aa 100644
--- a/test/unit/gateways/orbital_test.rb
+++ b/test/unit/gateways/orbital_test.rb
@@ -1047,6 +1047,46 @@ def test_stored_credential_installment_mit_used
assert_success response
end
+ def test_successful_store_request
+ stub_comms do
+ @gateway.store(credit_card, @options)
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match %{GT}, data
+ end
+ end
+
+ def test_successful_payment_request_with_token_stored
+ options = @options.merge(card_brand: 'MC', token_txn_type: 'UT')
+ stub_comms do
+ @gateway.purchase(50, '2521002395820006', options)
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match %{MC}, data
+ assert_match %{2521002395820006}, data
+ end
+ end
+
+ def test_successful_store
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+
+ assert response = @gateway.store(@credit_card, @options)
+ assert_instance_of Response, response
+ assert_equal 'Approved', response.message
+ assert_equal '4556761607723886', response.params['safetech_token']
+ assert_equal 'VI', response.params['card_brand']
+ end
+
+ def test_successful_purchase_with_stored_token
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ assert store = @gateway.store(@credit_card, @options)
+ assert_instance_of Response, store
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ assert auth = @gateway.purchase(100, store.authorization, @options)
+ assert_instance_of Response, auth
+
+ assert_equal 'Approved', auth.message
+ end
+
def test_successful_purchase_with_overridden_normalized_stored_credentials
stub_comms do
@gateway.purchase(50, credit_card, @options.merge(@normalized_mit_stored_credential).merge(@options_stored_credentials))
@@ -1757,6 +1797,10 @@ def successful_refund_response
'R253997001VI45567610299838860c1792db5d167e0b96dd9c60D1E12322FD50E1517A2598593A48EEA9965469201003 tst743Approved100 090955'
end
+ def successful_store_response
+ 'AC492310001VI4556761029983886f9269cbc7eb453d75adb1d6536A0990C37C45D0000082B0001A64E4156534A101007 Mtst443Approved100IUM123433455676160772388600A'
+ end
+
def failed_refund_response
'881The LIDM you supplied (3F3F3F) does not match with any existing transaction'
end
From 59edd7682d5f678fd841aa55a11cbc6df37b2dc0 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Wed, 8 Nov 2023 07:12:20 -0600
Subject: [PATCH 218/390] Quickbooks: Remove OAuth response from
refresh_access_token
---
CHANGELOG | 1 +
.../billing/gateways/quickbooks.rb | 15 +++++----------
test/remote/gateways/remote_quickbooks_test.rb | 17 -----------------
test/unit/gateways/quickbooks_test.rb | 9 ---------
4 files changed, 6 insertions(+), 36 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index bc1a3f5e704..b1c4e345603 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -73,6 +73,7 @@
* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
* Braintree: Add sca_exemption [almalee24] #4864
* Ebanx: Update Verify [almalee24] #4866
+* Quickbooks: Remove OAuth response from refresh_access_token [almalee24] #4949
== Version 1.134.0 (July 25, 2023)
* Update required Ruby version [almalee24] #4823
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index e7e2cba0018..1876d0db3f9 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -305,17 +305,12 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}
- begin
- response = ssl_post(REFRESH_URI, data, headers)
- rescue ResponseError => e
- raise OAuthResponseError.new(e)
- else
- json_response = JSON.parse(response)
+ response = ssl_post(REFRESH_URI, data, headers)
+ json_response = JSON.parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
- response
- end
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
+ response
end
def cvv_code_from(response)
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index 6b295967b22..f3457706af5 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -18,23 +18,6 @@ def setup
}
end
- def test_failed_access_token
- assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
- gateway.send :refresh_access_token
- end
- end
-
- def test_failed_purchase_with_failed_access_token
- gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
-
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- gateway.purchase(@amount, @credit_card, @options)
- end
-
- assert_equal error.message, 'Failed with 401 Unauthorized'
- end
-
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb
index 9b7a6f94a5b..7e48cce44ef 100644
--- a/test/unit/gateways/quickbooks_test.rb
+++ b/test/unit/gateways/quickbooks_test.rb
@@ -32,15 +32,6 @@ def setup
@authorization_no_request_id = 'ECZ7U0SO423E'
end
- def test_refresh_access_token_should_rise_an_exception_under_unauthorized
- error = assert_raises(ActiveMerchant::OAuthResponseError) do
- @oauth_2_gateway .expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
- @oauth_2_gateway .send(:refresh_access_token)
- end
-
- assert_match(/Failed with 401 Unauthorized/, error.message)
- end
-
def test_successful_purchase
[@oauth_1_gateway, @oauth_2_gateway].each do |gateway|
gateway.expects(:ssl_post).returns(successful_purchase_response)
From 73cb68d6d5c6ef7041bd8a5397001d63cf2c11f5 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Wed, 1 Nov 2023 17:04:06 -0700
Subject: [PATCH 219/390] Payeezy: Add customer_ref and reference_3 fields
---
CHANGELOG | 1 +
.../billing/gateways/payeezy.rb | 12 ++++++++++
test/remote/gateways/remote_payeezy_test.rb | 22 ++++++++++++++++++-
test/unit/gateways/payeezy_test.rb | 22 +++++++++++++++++++
4 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index b1c4e345603..c2fe53be078 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -51,6 +51,7 @@
* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
* Worldpay: Update 3ds logic to accept df_reference_id directly [DustinHaefele] #4929
* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
+* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index d8f685d2f71..8f4425cd0d5 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -35,6 +35,8 @@ def purchase(amount, payment_method, options = {})
add_invoice(params, options)
add_reversal_id(params, options)
+ add_customer_ref(params, options)
+ add_reference_3(params, options)
add_payment_method(params, payment_method, options)
add_address(params, options)
add_amount(params, amount, options)
@@ -51,6 +53,8 @@ def authorize(amount, payment_method, options = {})
add_invoice(params, options)
add_reversal_id(params, options)
+ add_customer_ref(params, options)
+ add_reference_3(params, options)
add_payment_method(params, payment_method, options)
add_address(params, options)
add_amount(params, amount, options)
@@ -170,6 +174,14 @@ def add_reversal_id(params, options)
params[:reversal_id] = options[:reversal_id] if options[:reversal_id]
end
+ def add_customer_ref(params, options)
+ params[:customer_ref] = options[:customer_ref] if options[:customer_ref]
+ end
+
+ def add_reference_3(params, options)
+ params[:reference_3] = options[:reference_3] if options[:reference_3]
+ end
+
def amount_from_authorization(authorization)
authorization.split('|').last.to_i
end
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
index 177511fa45c..da2fd0fae93 100644
--- a/test/remote/gateways/remote_payeezy_test.rb
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -122,6 +122,26 @@ def test_successful_purchase_with_soft_descriptors
assert_success response
end
+ def test_successful_purchase_and_authorize_with_reference_3
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(reference_3: '123345'))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(reference_3: '123345'))
+ assert_match(/Transaction Normal/, auth.message)
+ assert_success auth
+ end
+
+ def test_successful_purchase_and_authorize_with_customer_ref_top_level
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(customer_ref: 'abcde'))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(customer_ref: 'abcde'))
+ assert_match(/Transaction Normal/, auth.message)
+ assert_success auth
+ end
+
def test_successful_purchase_with_customer_ref
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(level2: { customer_ref: 'An important customer' }))
assert_match(/Transaction Normal/, response.message)
@@ -441,7 +461,7 @@ def test_trans_error
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_match(/Server Error/, response.message) # 42 is 'unable to send trans'
assert_failure response
- assert_equal '500', response.error_code
+ assert_equal '500 INTERNAL_SERVER_ERROR', response.error_code
end
def test_transcript_scrubbing_store
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index bb92e56e002..f95a219a8a4 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -221,6 +221,28 @@ def test_successful_purchase_with_customer_ref
assert_success response
end
+ def test_successful_purchase_with_customer_ref_top_level
+ options = @options.merge(customer_ref: 'abcde')
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"customer_ref":"abcde"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_reference_3
+ options = @options.merge(reference_3: '12345')
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"reference_3":"12345"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_successful_purchase_with_stored_credentials
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials))
From b9bbc887b482fc630b38616c112ace9bf0828aad Mon Sep 17 00:00:00 2001
From: aenand
Date: Wed, 8 Nov 2023 09:53:53 -0500
Subject: [PATCH 220/390] Redsys Rest
ECS-3219
This ticket adds a new gateway that serves as an update over the legacy
Redsys integration by moving to the Redsys Rest integration. This commit
adds support for basic functionality including authorize, purchase, scrubbing,
and standardizes responses.
This commit does not include support for:
* Stored Credentials (Ticket not yet created to add this support)
* External 3DS Support (Ticket not yet created to add this support)
* Redsys 3DS Support (Ticket created to add this support)
Co-authored-by: Amit Enand
Co-authored-by: Piers Chambers
---
CHANGELOG | 1 +
.../billing/gateways/redsys_rest.rb | 433 ++++++++++++++++++
test/fixtures.yml | 6 +
.../gateways/remote_redsys_rest_test.rb | 206 +++++++++
test/unit/gateways/redsys_rest_test.rb | 269 +++++++++++
5 files changed, 915 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/redsys_rest.rb
create mode 100644 test/remote/gateways/remote_redsys_rest_test.rb
create mode 100644 test/unit/gateways/redsys_rest_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index c2fe53be078..040abad97d6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -52,6 +52,7 @@
* Worldpay: Update 3ds logic to accept df_reference_id directly [DustinHaefele] #4929
* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
+* Redsys Rest: Add support for new gateway type Redsys Rest [aenand] #4951
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/redsys_rest.rb b/lib/active_merchant/billing/gateways/redsys_rest.rb
new file mode 100644
index 00000000000..02758e797a7
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/redsys_rest.rb
@@ -0,0 +1,433 @@
+# coding: utf-8
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # = Redsys Merchant Gateway
+ #
+ # Gateway support for the Spanish "Redsys" payment gateway system. This is
+ # used by many banks in Spain and is particularly well supported by
+ # Catalunya Caixa's ecommerce department.
+ #
+ # Redsys requires an order_id be provided with each transaction and it must
+ # follow a specific format. The rules are as follows:
+ #
+ # * First 4 digits must be numerical
+ # * Remaining 8 digits may be alphanumeric
+ # * Max length: 12
+ #
+ # If an invalid order_id is provided, we do our best to clean it up.
+ #
+ # Written by Piers Chambers (Varyonic.com)
+ #
+ # *** SHA256 Authentication Update ***
+ #
+ # Redsys has dropped support for the SHA1 authentication method.
+ # Developer documentation: https://pagosonline.redsys.es/desarrolladores.html
+ class RedsysRestGateway < Gateway
+ self.test_url = 'https://sis-t.redsys.es:25443/sis/rest/'
+ self.live_url = 'https://sis.redsys.es/sis/rest/'
+
+ self.supported_countries = ['ES']
+ self.default_currency = 'EUR'
+ self.money_format = :cents
+ # Not all card types may be activated by the bank!
+ self.supported_cardtypes = %i[visa master american_express jcb diners_club unionpay]
+ self.homepage_url = 'http://www.redsys.es/'
+ self.display_name = 'Redsys (REST)'
+
+ CURRENCY_CODES = {
+ 'AED' => '784',
+ 'ARS' => '32',
+ 'AUD' => '36',
+ 'BRL' => '986',
+ 'BOB' => '68',
+ 'CAD' => '124',
+ 'CHF' => '756',
+ 'CLP' => '152',
+ 'CNY' => '156',
+ 'COP' => '170',
+ 'CRC' => '188',
+ 'CZK' => '203',
+ 'DKK' => '208',
+ 'DOP' => '214',
+ 'EUR' => '978',
+ 'GBP' => '826',
+ 'GTQ' => '320',
+ 'HUF' => '348',
+ 'IDR' => '360',
+ 'INR' => '356',
+ 'JPY' => '392',
+ 'KRW' => '410',
+ 'MYR' => '458',
+ 'MXN' => '484',
+ 'NOK' => '578',
+ 'NZD' => '554',
+ 'PEN' => '604',
+ 'PLN' => '985',
+ 'RUB' => '643',
+ 'SAR' => '682',
+ 'SEK' => '752',
+ 'SGD' => '702',
+ 'THB' => '764',
+ 'TWD' => '901',
+ 'USD' => '840',
+ 'UYU' => '858'
+ }
+
+ # The set of supported transactions for this gateway.
+ # More operations are supported by the gateway itself, but
+ # are not supported in this library.
+ SUPPORTED_TRANSACTIONS = {
+ purchase: '0',
+ authorize: '1',
+ capture: '2',
+ refund: '3',
+ cancel: '9'
+ }
+
+ # These are the text meanings sent back by the acquirer when
+ # a card has been rejected. Syntax or general request errors
+ # are not covered here.
+ RESPONSE_TEXTS = {
+ 0 => 'Transaction Approved',
+ 400 => 'Cancellation Accepted',
+ 481 => 'Cancellation Accepted',
+ 500 => 'Reconciliation Accepted',
+ 900 => 'Refund / Confirmation approved',
+
+ 101 => 'Card expired',
+ 102 => 'Card blocked temporarily or under susciption of fraud',
+ 104 => 'Transaction not permitted',
+ 107 => 'Contact the card issuer',
+ 109 => 'Invalid identification by merchant or POS terminal',
+ 110 => 'Invalid amount',
+ 114 => 'Card cannot be used to the requested transaction',
+ 116 => 'Insufficient credit',
+ 118 => 'Non-registered card',
+ 125 => 'Card not effective',
+ 129 => 'CVV2/CVC2 Error',
+ 167 => 'Contact the card issuer: suspected fraud',
+ 180 => 'Card out of service',
+ 181 => 'Card with credit or debit restrictions',
+ 182 => 'Card with credit or debit restrictions',
+ 184 => 'Authentication error',
+ 190 => 'Refusal with no specific reason',
+ 191 => 'Expiry date incorrect',
+ 195 => 'Requires SCA authentication',
+
+ 201 => 'Card expired',
+ 202 => 'Card blocked temporarily or under suspicion of fraud',
+ 204 => 'Transaction not permitted',
+ 207 => 'Contact the card issuer',
+ 208 => 'Lost or stolen card',
+ 209 => 'Lost or stolen card',
+ 280 => 'CVV2/CVC2 Error',
+ 290 => 'Declined with no specific reason',
+
+ 480 => 'Original transaction not located, or time-out exceeded',
+ 501 => 'Original transaction not located, or time-out exceeded',
+ 502 => 'Original transaction not located, or time-out exceeded',
+ 503 => 'Original transaction not located, or time-out exceeded',
+
+ 904 => 'Merchant not registered at FUC',
+ 909 => 'System error',
+ 912 => 'Issuer not available',
+ 913 => 'Duplicate transmission',
+ 916 => 'Amount too low',
+ 928 => 'Time-out exceeded',
+ 940 => 'Transaction cancelled previously',
+ 941 => 'Authorization operation already cancelled',
+ 942 => 'Original authorization declined',
+ 943 => 'Different details from origin transaction',
+ 944 => 'Session error',
+ 945 => 'Duplicate transmission',
+ 946 => 'Cancellation of transaction while in progress',
+ 947 => 'Duplicate tranmission while in progress',
+ 949 => 'POS Inoperative',
+ 950 => 'Refund not possible',
+ 9064 => 'Card number incorrect',
+ 9078 => 'No payment method available',
+ 9093 => 'Non-existent card',
+ 9218 => 'Recursive transaction in bad gateway',
+ 9253 => 'Check-digit incorrect',
+ 9256 => 'Preauth not allowed for merchant',
+ 9257 => 'Preauth not allowed for card',
+ 9261 => 'Operating limit exceeded',
+ 9912 => 'Issuer not available',
+ 9913 => 'Confirmation error',
+ 9914 => 'KO Confirmation'
+ }
+
+ # Expected values as per documentation
+ THREE_DS_V2 = '2.1.0'
+
+ # Creates a new instance
+ #
+ # Redsys requires a login and secret_key, and optionally also accepts a
+ # non-default terminal.
+ #
+ # ==== Options
+ #
+ # * :login -- The Redsys Merchant ID (REQUIRED)
+ # * :secret_key -- The Redsys Secret Key. (REQUIRED)
+ # * :terminal -- The Redsys Terminal. Defaults to 1. (OPTIONAL)
+ # * :test -- +true+ or +false+. Defaults to +false+. (OPTIONAL)
+ def initialize(options = {})
+ requires!(options, :login, :secret_key)
+ options[:terminal] ||= 1
+ options[:signature_algorithm] = 'sha256'
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :purchase, options)
+ add_amount(post, money, options)
+ add_order(post, options[:order_id])
+ add_payment(post, payment)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
+ end
+
+ def authorize(money, payment, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :authorize, options)
+ add_amount(post, money, options)
+ add_order(post, options[:order_id])
+ add_payment(post, payment)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_action(post, :capture)
+ add_amount(post, money, options)
+ order_id, = split_authorization(authorization)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def void(authorization, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :cancel)
+ order_id, amount, currency = split_authorization(authorization)
+ add_amount(post, amount, currency: currency)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def refund(money, authorization, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :refund)
+ add_amount(post, money, options)
+ order_id, = split_authorization(authorization)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def verify(creditcard, options = {})
+ requires!(options, :order_id)
+
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((PAN\"=>\")(\d+)), '\1[FILTERED]').
+ gsub(%r((CVV2\"=>\")(\d+)), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_direct_payment(post, options)
+ # Direct payment skips 3DS authentication. We should only apply this if execute_threed is false
+ # or authentication data is not present. Authentication data support to be added in the future.
+ return if options[:execute_threed] || options[:authentication_data]
+
+ post[:DS_MERCHANT_DIRECTPAYMENT] = true
+ end
+
+ def add_threeds(post, options)
+ post[:DS_MERCHANT_EMV3DS] = { threeDSInfo: 'CardData' } if options[:execute_threed]
+ end
+
+ def add_action(post, action, options = {})
+ post[:DS_MERCHANT_TRANSACTIONTYPE] = transaction_code(action)
+ end
+
+ def add_amount(post, money, options)
+ post[:DS_MERCHANT_AMOUNT] = amount(money).to_s
+ post[:DS_MERCHANT_CURRENCY] = currency_code(options[:currency] || currency(money))
+ end
+
+ def add_description(post, options)
+ post[:DS_MERCHANT_PRODUCTDESCRIPTION] = CGI.escape(options[:description]) if options[:description]
+ end
+
+ def add_order(post, order_id)
+ post[:DS_MERCHANT_ORDER] = clean_order_id(order_id)
+ end
+
+ def add_payment(post, card)
+ name = [card.first_name, card.last_name].join(' ').slice(0, 60)
+ year = sprintf('%.4i', card.year)
+ month = sprintf('%.2i', card.month)
+ post['DS_MERCHANT_TITULAR'] = CGI.escape(name)
+ post['DS_MERCHANT_PAN'] = card.number
+ post['DS_MERCHANT_EXPIRYDATE'] = "#{year[2..3]}#{month}"
+ post['DS_MERCHANT_CVV2'] = card.verification_value
+ end
+
+ def determine_action(options)
+ # If execute_threed is true, we need to use iniciaPeticionREST to set up authentication
+ # Otherwise we are skipping 3DS or we should have 3DS authentication results
+ options[:execute_threed] ? 'iniciaPeticionREST' : 'trataPeticionREST'
+ end
+
+ def commit(post, options)
+ url = (test? ? test_url : live_url)
+ action = determine_action(options)
+ raw_response = parse(ssl_post(url + action, post_data(post, options)))
+ payload = raw_response['Ds_MerchantParameters']
+ return Response.new(false, "#{raw_response['errorCode']} ERROR") unless payload
+
+ response = JSON.parse(Base64.decode64(payload)).transform_keys!(&:downcase).with_indifferent_access
+ return Response.new(false, 'Unable to verify response') unless validate_signature(payload, raw_response['Ds_Signature'], response[:ds_order])
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: success_from(response) ? nil : response[:ds_response]
+ )
+ end
+
+ def post_data(post, options)
+ add_authentication(post, options)
+ merchant_parameters = JSON.generate(post)
+ encoded_parameters = Base64.strict_encode64(merchant_parameters)
+ post_data = PostData.new
+ post_data['Ds_SignatureVersion'] = 'HMAC_SHA256_V1'
+ post_data['Ds_MerchantParameters'] = encoded_parameters
+ post_data['Ds_Signature'] = sign_request(encoded_parameters, post[:DS_MERCHANT_ORDER])
+ post_data.to_post_data
+ end
+
+ def add_authentication(post, options)
+ post[:DS_MERCHANT_TERMINAL] = options[:terminal] || @options[:terminal]
+ post[:DS_MERCHANT_MERCHANTCODE] = @options[:login]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ # Need to get updated for 3DS support
+ if code = response[:ds_response]
+ (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
+ else
+ false
+ end
+ end
+
+ def message_from(response)
+ # Need to get updated for 3DS support
+ code = response[:ds_response]&.to_i
+ code = 0 if code < 100
+ RESPONSE_TEXTS[code] || 'Unknown code, please check in manual'
+ end
+
+ def validate_signature(data, signature, order_number)
+ key = encrypt(@options[:secret_key], order_number)
+ Base64.urlsafe_encode64(mac256(key, data)) == signature
+ end
+
+ def authorization_from(response)
+ # Need to get updated for 3DS support
+ [response[:ds_order], response[:ds_amount], response[:ds_currency]].join('|')
+ end
+
+ def split_authorization(authorization)
+ order_id, amount, currency = authorization.split('|')
+ [order_id, amount.to_i, currency]
+ end
+
+ def currency_code(currency)
+ return currency if currency =~ /^\d+$/
+ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency]
+
+ CURRENCY_CODES[currency]
+ end
+
+ def transaction_code(type)
+ SUPPORTED_TRANSACTIONS[type]
+ end
+
+ def clean_order_id(order_id)
+ cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
+ if /^\d{4}/.match?(cleansed)
+ cleansed[0..11]
+ else
+ '%04d' % [rand(0..9999)] + cleansed[0...8]
+ end
+ end
+
+ def sign_request(encoded_parameters, order_id)
+ raise(ArgumentError, 'missing order_id') unless order_id
+
+ key = encrypt(@options[:secret_key], order_id)
+ Base64.strict_encode64(mac256(key, encoded_parameters))
+ end
+
+ def encrypt(key, order_id)
+ block_length = 8
+ cipher = OpenSSL::Cipher.new('DES3')
+ cipher.encrypt
+
+ cipher.key = Base64.urlsafe_decode64(key)
+ # The OpenSSL default of an all-zeroes ("\\0") IV is used.
+ cipher.padding = 0
+
+ order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros
+
+ output = cipher.update(order_id) + cipher.final
+ output
+ end
+
+ def mac256(key, data)
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
+ end
+ end
+ end
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 7f3606fbc68..7cecdaafde8 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -1242,6 +1242,12 @@ redsys:
login: MERCHANT CODE
secret_key: SECRET KEY
+# https://pagosonline.redsys.es/entornosPruebas.html
+redsys_rest:
+ login: 999008881
+ secret_key: sq7HjrUOBfKmC576ILgskD5srU870gJ7
+ terminal: 001
+
redsys_sha256:
login: MERCHANT CODE
secret_key: SECRET KEY
diff --git a/test/remote/gateways/remote_redsys_rest_test.rb b/test/remote/gateways/remote_redsys_rest_test.rb
new file mode 100644
index 00000000000..44737703446
--- /dev/null
+++ b/test/remote/gateways/remote_redsys_rest_test.rb
@@ -0,0 +1,206 @@
+require 'test_helper'
+
+class RemoteRedsysRestTest < Test::Unit::TestCase
+ def setup
+ @gateway = RedsysRestGateway.new(fixtures(:redsys_rest))
+ @amount = 100
+ @credit_card = credit_card('4548812049400004')
+ @declined_card = credit_card
+ @threeds2_credit_card = credit_card('4918019199883839')
+
+ @threeds2_credit_card_frictionless = credit_card('4548814479727229')
+ @threeds2_credit_card_alt = credit_card('4548817212493017')
+ @options = {
+ order_id: generate_order_id
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_purchase_with_invalid_order_id
+ response = @gateway.purchase(@amount, @credit_card, order_id: "a%4#{generate_order_id}")
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_purchase_and_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_purchase_and_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(@amount + 100, purchase.authorization, @options)
+ assert_failure refund
+ assert_equal 'SIS0057 ERROR', refund.message
+ end
+
+ def test_failed_purchase_with_unsupported_currency
+ response = @gateway.purchase(600, @credit_card, @options.merge(currency: 'PEN'))
+ assert_failure response
+ assert_equal 'SIS0027 ERROR', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success capture
+ assert_match(/Refund.*approved/, capture.message)
+ end
+
+ def test_successful_authorize_and_failed_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(2 * @amount, authorize.authorization, @options)
+ assert_failure capture
+ assert_match(/SIS0062 ERROR/, capture.message)
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization, @options)
+ assert_success void
+ assert_equal '100', void.params['ds_amount']
+ assert_equal 'Cancellation Accepted', void.message
+ end
+
+ def test_failed_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ authorization = "#{authorize.params[:ds_order]}|#{@amount}|203"
+ void = @gateway.void(authorization, @options)
+ assert_failure void
+ assert_equal 'SIS0027 ERROR', void.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Approved', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'Cancellation Accepted', response.responses.last.message
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_transcript_scrubbing_on_failed_transactions
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @declined_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_encrypt_handles_url_safe_character_in_secret_key_without_error
+ gateway = RedsysRestGateway.new({
+ login: '091952713',
+ secret_key: 'yG78qf-PkHyRzRiZGSTCJdO2TvjWgFa8'
+ })
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert response
+ end
+
+ # Pending 3DS support
+ # def test_successful_authorize_3ds_setup
+ # options = @options.merge(execute_threed: true, terminal: 12)
+ # response = @gateway.authorize(@amount, @credit_card, options)
+ # assert_success response
+ # assert response.params['ds_emv3ds']
+ # assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ # assert_equal 'CardConfiguration', response.message
+ # assert response.authorization
+ # end
+
+ # Pending 3DS support
+ # def test_successful_purchase_3ds
+ # options = @options.merge(execute_threed: true)
+ # response = @gateway.purchase(@amount, @threeds2_credit_card, options)
+ # assert_success response
+ # assert three_ds_data = JSON.parse(response.params['ds_emv3ds'])
+ # assert_equal '2.1.0', three_ds_data['protocolVersion']
+ # assert_equal 'https://sis-d.redsys.es/sis-simulador-web/threeDsMethod.jsp', three_ds_data['threeDSMethodURL']
+ # assert_equal 'CardConfiguration', response.message
+ # assert response.authorization
+ # end
+
+ # Pending 3DS support
+ # Requires account configuration to allow setting moto flag
+ # def test_purchase_with_moto_flag
+ # response = @gateway.purchase(@amount, @credit_card, @options.merge(moto: true, metadata: { manual_entry: true }))
+ # assert_equal 'SIS0488 ERROR', response.message
+ # end
+
+ # Pending 3DS support
+ # def test_successful_3ds_authorize_with_exemption
+ # options = @options.merge(execute_threed: true, terminal: 12)
+ # response = @gateway.authorize(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
+ # assert_success response
+ # assert response.params['ds_emv3ds']
+ # assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ # assert_equal 'CardConfiguration', response.message
+ # end
+
+ # Pending 3DS support
+ # def test_successful_3ds_purchase_with_exemption
+ # options = @options.merge(execute_threed: true, terminal: 12)
+ # response = @gateway.purchase(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
+ # assert_success response
+ # assert response.params['ds_emv3ds']
+ # assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ # assert_equal 'CardConfiguration', response.message
+ # end
+
+ private
+
+ def generate_order_id
+ (Time.now.to_f * 100).to_i.to_s
+ end
+end
diff --git a/test/unit/gateways/redsys_rest_test.rb b/test/unit/gateways/redsys_rest_test.rb
new file mode 100644
index 00000000000..e8b9b631629
--- /dev/null
+++ b/test/unit/gateways/redsys_rest_test.rb
@@ -0,0 +1,269 @@
+require 'test_helper'
+
+class RedsysRestTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @credentials = {
+ login: '091952713',
+ secret_key: 'sq7HjrUOBfKmC576ILgskD5srU870gJ7',
+ terminal: '201'
+ }
+ @gateway = RedsysRestGateway.new(@credentials)
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ order_id: '1001',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ res = @gateway.purchase(123, credit_card, @options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '164513224019|100|978', res.authorization
+ assert_equal '164513224019', res.params['ds_order']
+ end
+
+ def test_successful_purchase_requesting_credit_card_token
+ @gateway.expects(:ssl_post).returns(successful_purchase_response_with_credit_card_token)
+ res = @gateway.purchase(123, credit_card, @options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '164522070945|100|978', res.authorization
+ assert_equal '164522070945', res.params['ds_order']
+ assert_equal '2202182245100', res.params['ds_merchant_cof_txnid']
+ end
+
+ def test_successful_purchase_with_stored_credentials
+ @gateway.expects(:ssl_post).returns(successful_purchase_initial_stored_credential_response)
+ initial_options = @options.merge(
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'recurring'
+ }
+ )
+ initial_res = @gateway.purchase(123, credit_card, initial_options)
+ assert_success initial_res
+ assert_equal 'Transaction Approved', initial_res.message
+ assert_equal '2205022148020', initial_res.params['ds_merchant_cof_txnid']
+ network_transaction_id = initial_res.params['Ds_Merchant_Cof_Txnid']
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_used_stored_credential_response)
+ used_options = {
+ order_id: '1002',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'unscheduled',
+ network_transaction_id: network_transaction_id
+ }
+ }
+ res = @gateway.purchase(123, credit_card, used_options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '446527', res.params['ds_authorisationcode']
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
+ res = @gateway.purchase(123, credit_card, @options)
+ assert_failure res
+ assert_equal 'Refusal with no specific reason', res.message
+ assert_equal '164513457405', res.params['ds_order']
+ end
+
+ def test_purchase_without_order_id
+ assert_raise ArgumentError do
+ @gateway.purchase(123, credit_card)
+ end
+ end
+
+ def test_error_purchase
+ @gateway.expects(:ssl_post).returns(error_purchase_response)
+ res = @gateway.purchase(123, credit_card, @options)
+ assert_failure res
+ assert_equal 'SIS0051 ERROR', res.message
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ res = @gateway.authorize(123, credit_card, @options)
+ assert_success res
+ assert_equal 'Transaction Approved', res.message
+ assert_equal '165125433469|100|978', res.authorization
+ assert_equal '165125433469', res.params['ds_order']
+ end
+
+ def test_failed_authorize
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ res = @gateway.authorize(123, credit_card, @options)
+ assert_failure res
+ assert_equal 'Refusal with no specific reason', res.message
+ assert_equal '165125669647', res.params['ds_order']
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+ res = @gateway.capture(123, '165125709531|100|978', @options)
+ assert_success res
+ assert_equal 'Refund / Confirmation approved', res.message
+ assert_equal '165125709531|100|978', res.authorization
+ assert_equal '165125709531', res.params['ds_order']
+ end
+
+ def test_error_capture
+ @gateway.expects(:ssl_post).returns(error_capture_response)
+ res = @gateway.capture(123, '165125709531|100|978', @options)
+ assert_failure res
+ assert_equal 'SIS0062 ERROR', res.message
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ res = @gateway.refund(123, '165126074048|100|978', @options)
+ assert_success res
+ assert_equal 'Refund / Confirmation approved', res.message
+ assert_equal '165126074048|100|978', res.authorization
+ assert_equal '165126074048', res.params['ds_order']
+ end
+
+ def test_error_refund
+ @gateway.expects(:ssl_post).returns(error_refund_response)
+ res = @gateway.refund(123, '165126074048|100|978', @options)
+ assert_failure res
+ assert_equal 'SIS0057 ERROR', res.message
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+ res = @gateway.void('165126313156|100|978', @options)
+ assert_success res
+ assert_equal 'Cancellation Accepted', res.message
+ assert_equal '165126313156|100|978', res.authorization
+ assert_equal '165126313156', res.params['ds_order']
+ end
+
+ def test_error_void
+ @gateway.expects(:ssl_post).returns(error_void_response)
+ res = @gateway.void('165126074048|100|978', @options)
+ assert_failure res
+ assert_equal 'SIS0222 ERROR', res.message
+ end
+
+ def test_successful_verify
+ @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response)
+ response = @gateway.verify(credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_verify_with_failed_void
+ @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(error_void_response)
+ response = @gateway.verify(credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_unsuccessful_verify
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ response = @gateway.verify(credit_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_unknown_currency
+ assert_raise ArgumentError do
+ @gateway.purchase(123, credit_card, @options.merge(currency: 'HUH WUT'))
+ end
+ end
+
+ def test_default_currency
+ assert_equal 'EUR', RedsysRestGateway.default_currency
+ end
+
+ def test_supported_countries
+ assert_equal ['ES'], RedsysRestGateway.supported_countries
+ end
+
+ def test_supported_cardtypes
+ assert_equal %i[visa master american_express jcb diners_club unionpay], RedsysRestGateway.supported_cardtypes
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ private
+
+ def pre_scrubbed
+ '
+ merchant_parameters: {"DS_MERCHANT_CURRENCY"=>"978", "DS_MERCHANT_AMOUNT"=>"100", "DS_MERCHANT_ORDER"=>"165126475243", "DS_MERCHANT_TRANSACTIONTYPE"=>"1", "DS_MERCHANT_PRODUCTDESCRIPTION"=>"", "DS_MERCHANT_TERMINAL"=>"3", "DS_MERCHANT_MERCHANTCODE"=>"327234688", "DS_MERCHANT_TITULAR"=>"Longbob Longsen", "DS_MERCHANT_PAN"=>"4242424242424242", "DS_MERCHANT_EXPIRYDATE"=>"2309", "DS_MERCHANT_CVV2"=>"123"}
+ '
+ end
+
+ def post_scrubbed
+ '
+ merchant_parameters: {"DS_MERCHANT_CURRENCY"=>"978", "DS_MERCHANT_AMOUNT"=>"100", "DS_MERCHANT_ORDER"=>"165126475243", "DS_MERCHANT_TRANSACTIONTYPE"=>"1", "DS_MERCHANT_PRODUCTDESCRIPTION"=>"", "DS_MERCHANT_TERMINAL"=>"3", "DS_MERCHANT_MERCHANTCODE"=>"327234688", "DS_MERCHANT_TITULAR"=>"Longbob Longsen", "DS_MERCHANT_PAN"=>"[FILTERED]", "DS_MERCHANT_EXPIRYDATE"=>"2309", "DS_MERCHANT_CVV2"=>"[FILTERED]"}
+ '
+ end
+
+ def successful_purchase_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY0NTEzMjI0MDE5IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0ODgxODUiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NDUxMzIyNDE0NDkiOiIxNjQ1MTMyMjQxNDQ5In0=\",\"Ds_Signature\":\"63UXUOSVheJiBWxaWKih5yaVvfOSeOXAuoRUZyHBwJo=\"}]
+ end
+
+ def successful_purchase_response_with_credit_card_token
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY0NTIyMDcwOTQ1IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0ODk5MTciLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX01lcmNoYW50X0NvZl9UeG5pZCI6IjIyMDIxODIyNDUxMDAiLCJEc19Qcm9jZXNzZWRQYXlNZXRob2QiOiIzIiwiRHNfQ29udHJvbF8xNjQ1MjIwNzEwNDcyIjoiMTY0NTIyMDcxMDQ3MiJ9\",\"Ds_Signature\":\"YV6W2Ym-p84q5246GK--hc-1L6Sz0tHOcMLYZtDIf-s=\"}]
+ end
+
+ def successful_purchase_initial_stored_credential_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTUyMDg4MTM3IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NTk5MjIiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX01lcmNoYW50X0NvZl9UeG5pZCI6IjIyMDUwMjIxNDgwMjAiLCJEc19Qcm9jZXNzZWRQYXlNZXRob2QiOiIzIiwiRHNfQ29udHJvbF8xNjUxNTIwODgyNDA5IjoiMTY1MTUyMDg4MjQwOSJ9\",\"Ds_Signature\":\"gIQ6ebPg-nXwCZ0Vld7LbSoKBXizlmaVe1djVDuVF4s=\"}]
+ end
+
+ def successful_purchase_used_stored_credential_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTUyMDg4MjQ0IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NDY1MjciLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NTE1MjA4ODMzMDMiOiIxNjUxNTIwODgzMzAzIn0=\",\"Ds_Signature\":\"BC3UB0Q0IgOyuXbEe8eJddK_H77XJv7d2MQr50d4v2o=\"}]
+ end
+
+ def failed_purchase_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY0NTEzNDU3NDA1IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMTkwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiIiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI4MjYiLCJEc19Qcm9jZXNzZWRQYXlNZXRob2QiOiIzIiwiRHNfQ29udHJvbF8xNjQ1MTM0NTc1MzU1IjoiMTY0NTEzNDU3NTM1NSJ9\",\"Ds_Signature\":\"zm3FCtPPhf5Do7FzlB4DbGDgkFcNFhXQCikc-batUW0=\"}]
+ end
+
+ def error_purchase_response
+ %[{\"errorCode\":\"SIS0051\"}]
+ end
+
+ def successful_authorize_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTI1NDMzNDY5IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NTgyNjAiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIxIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NTEyNTQzMzYzMTEiOiIxNjUxMjU0MzM2MzExIn0=\",\"Ds_Signature\":\"8H7F04WLREFYi67DxusWJX12NZOrMrmtDOVWYA-604M=\"}]
+ end
+
+ def failed_authorize_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTI1NjY5NjQ3IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMTkwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiIiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIxIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI4MjYiLCJEc19Qcm9jZXNzZWRQYXlNZXRob2QiOiIzIiwiRHNfQ29udHJvbF8xNjUxMjU2Njk4MDE0IjoiMTY1MTI1NjY5ODAxNCJ9\",\"Ds_Signature\":\"abBYZFLtYloFRQDTnMhXASMcS-4SLxEBNpTfBVCBtuc=\"}]
+ end
+
+ def successful_capture_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTI1NzA5NTMxIiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwOTAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NDQ5NTIiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIyIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NTEyNTcwOTc5NjIiOiIxNjUxMjU3MDk3OTYyIn0=\",\"Ds_Signature\":\"9lKWSe94kdviKN_ApUV9nQAS6VQc7gPeARyhpbN3sXA=\"}]
+ end
+
+ def error_capture_response
+ %[{\"errorCode\":\"SIS0062\"}]
+ end
+
+ def successful_refund_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTI2MDc0MDQ4IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwOTAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NDQ5NjQiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIzIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NTEyNjA3NDM0NjAiOiIxNjUxMjYwNzQzNDYwIn0=\",\"Ds_Signature\":\"iGhvjtqbV-b3cvEoJxIwp3kE1b65onfZnF9Kb5JWWhw=\"}]
+ end
+
+ def error_refund_response
+ %[{\"errorCode\":\"SIS0057\"}]
+ end
+
+ def successful_void_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY1MTI2MzEzMTU2IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwNDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0NTgzMDQiLCJEc19UcmFuc2FjdGlvblR5cGUiOiI5IiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NTEyNjMxMzQzMzUiOiIxNjUxMjYzMTM0MzM1In0=\",\"Ds_Signature\":\"retARpDayWGhU-pa3OEBIT7b4iG91Mi98jHGB3EyD6c=\"}]
+ end
+
+ def error_void_response
+ %[{\"errorCode\":\"SIS0222\"}]
+ end
+end
From 88842f1b5019fd5b00be6a5ec7a3a12db8745bb3 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Fri, 27 Oct 2023 11:59:40 -0700
Subject: [PATCH 221/390] CyberSource: surface the reconciliationID2 field if
present
---
CHANGELOG | 1 +
.../billing/gateways/cyber_source.rb | 1 +
.../gateways/remote_cyber_source_test.rb | 6 +++++
test/unit/gateways/cyber_source_test.rb | 26 +++++++++++++++++++
4 files changed, 34 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index 040abad97d6..e6e7edfc423 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -53,6 +53,7 @@
* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
* Redsys Rest: Add support for new gateway type Redsys Rest [aenand] #4951
+* CyberSource: surface the reconciliationID2 field [yunnydang] #4934
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index d9a6cc996e1..3aa95b99045 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -1161,6 +1161,7 @@ def parse_element(reply, node)
parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
parent += '_'
end
+ reply[:reconciliationID2] = node.text if node.name == 'reconciliationID' && reply[:reconciliationID]
reply["#{parent}#{node.name}".to_sym] ||= node.text
end
return reply
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 7f235ad910a..9e28a37fa59 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -493,6 +493,12 @@ def test_successful_purchase_with_reconciliation_id
assert_successful_response(response)
end
+ def test_successful_purchase_with_reconciliation_id_2
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ assert response.params['reconciliationID2']
+ end
+
def test_successful_authorize_with_customer_id
options = @options.merge(customer_id: '7500BB199B4270EFE05348D0AFCAD')
assert response = @gateway.authorize(@amount, @credit_card, options)
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 0e67c44700c..2b7c12929d7 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -568,6 +568,24 @@ def test_successful_auth_request
assert response.test?
end
+ def test_successful_reconciliation_id_2
+ @gateway.stubs(:ssl_post).returns(successful_purchase_and_capture_response)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal response.params['reconciliationID'], 'abcdf'
+ assert_equal response.params['reconciliationID2'], '31159291T3XM2B13'
+ assert response.success?
+ assert response.test?
+ end
+
+ def test_successful_authorization_without_reconciliation_id_2
+ @gateway.stubs(:ssl_post).returns(successful_authorization_response)
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal response.params['reconciliationID2'], nil
+ assert_equal response.params['reconciliationID'], '23439130C40VZ2FB'
+ assert response.success?
+ assert response.test?
+ end
+
def test_successful_auth_with_elo_request
@gateway.stubs(:ssl_post).returns(successful_authorization_response)
assert response = @gateway.authorize(@amount, @elo_credit_card, @options)
@@ -1783,6 +1801,14 @@ def successful_purchase_response
XML
end
+ def successful_purchase_and_capture_response
+ <<~XML
+
+
+ 2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00abcdf123456YYMM2008-01-15T21:42:03Z00U1002007-07-17T17:15:32Z1.0031159291T3XM2B13
+ XML
+ end
+
def successful_authorization_response
<<~XML
From 383e35f4495c715dde5ce0741a4f2eaf4c98bd45 Mon Sep 17 00:00:00 2001
From: Dustin A Haefele <45601251+DustinHaefele@users.noreply.github.com>
Date: Tue, 14 Nov 2023 13:22:12 -0500
Subject: [PATCH 222/390] Worldpay: Update stored credentials logic (#4950)
Remote tests:
99 tests, 429 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit tests:
116 tests, 658 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Spreedly ticket: ECS-3238
---
.../billing/gateways/worldpay.rb | 42 +++---
test/remote/gateways/remote_worldpay_test.rb | 122 +++++++++++-------
test/unit/gateways/worldpay_test.rb | 41 ++++++
3 files changed, 142 insertions(+), 63 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index b6ba7faf243..e66fccc79f1 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -684,30 +684,27 @@ def add_stored_credential_options(xml, options = {})
end
def add_stored_credential_using_normalized_fields(xml, options)
- if options[:stored_credential][:initial_transaction]
- xml.storedCredentials 'usage' => 'FIRST'
- else
- reason = case options[:stored_credential][:reason_type]
- when 'installment' then 'INSTALMENT'
- when 'recurring' then 'RECURRING'
- when 'unscheduled' then 'UNSCHEDULED'
- end
-
- xml.storedCredentials 'usage' => 'USED', 'merchantInitiatedReason' => reason do
- xml.schemeTransactionIdentifier options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
- end
+ reason = case options[:stored_credential][:reason_type]
+ when 'installment' then 'INSTALMENT'
+ when 'recurring' then 'RECURRING'
+ when 'unscheduled' then 'UNSCHEDULED'
+ end
+ is_initial_transaction = options[:stored_credential][:initial_transaction]
+ stored_credential_params = generate_stored_credential_params(is_initial_transaction, reason)
+
+ xml.storedCredentials stored_credential_params do
+ xml.schemeTransactionIdentifier options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] && !is_initial_transaction
end
end
def add_stored_credential_using_gateway_specific_fields(xml, options)
return unless options[:stored_credential_usage]
- if options[:stored_credential_initiated_reason]
- xml.storedCredentials 'usage' => options[:stored_credential_usage], 'merchantInitiatedReason' => options[:stored_credential_initiated_reason] do
- xml.schemeTransactionIdentifier options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id]
- end
- else
- xml.storedCredentials 'usage' => options[:stored_credential_usage]
+ is_initial_transaction = options[:stored_credential_usage] == 'FIRST'
+ stored_credential_params = generate_stored_credential_params(is_initial_transaction, options[:stored_credential_initiated_reason])
+
+ xml.storedCredentials stored_credential_params do
+ xml.schemeTransactionIdentifier options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id] && !is_initial_transaction
end
end
@@ -1027,6 +1024,15 @@ def eligible_for_0_auth?(payment_method, options = {})
def card_holder_name(payment_method, options)
test? && options[:execute_threed] && !options[:three_ds_version]&.start_with?('2') ? '3D' : payment_method.name
end
+
+ def generate_stored_credential_params(is_initial_transaction, reason = nil)
+ customer_or_merchant = reason == 'RECURRING' && is_initial_transaction ? 'customerInitiatedReason' : 'merchantInitiatedReason'
+
+ stored_credential_params = {}
+ stored_credential_params['usage'] = is_initial_transaction ? 'FIRST' : 'USED'
+ stored_credential_params[customer_or_merchant] = reason if reason
+ stored_credential_params
+ end
end
end
end
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index cf90dfe3e28..c59571cf98e 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -488,12 +488,7 @@ def test_successful_authorize_with_3ds2_challenge
end
def test_successful_auth_and_capture_with_normalized_stored_credential
- stored_credential_params = {
- initial_transaction: true,
- reason_type: 'unscheduled',
- initiator: 'merchant',
- network_transaction_id: nil
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params }))
assert_success auth
@@ -505,12 +500,31 @@ def test_successful_auth_and_capture_with_normalized_stored_credential
assert_success capture
@options[:order_id] = generate_unique_id
- @options[:stored_credential] = {
- initial_transaction: false,
- reason_type: 'installment',
- initiator: 'merchant',
- network_transaction_id: auth.params['transaction_identifier']
- }
+ @options[:stored_credential] = stored_credential(:used, :installment, :merchant, network_transaction_id: auth.params['transaction_identifier'])
+
+ assert next_auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert next_auth.authorization
+ assert next_auth.params['scheme_response']
+ assert next_auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
+ def test_successful_auth_and_capture_with_normalized_recurring_stored_credential
+ stored_credential_params = stored_credential(:initial, :recurring, :merchant)
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params }))
+ assert_success auth
+ assert auth.authorization
+ assert auth.params['scheme_response']
+ assert auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+
+ @options[:order_id] = generate_unique_id
+ @options[:stored_credential] = stored_credential(:used, :recurring, :merchant, network_transaction_id: auth.params['transaction_identifier'])
assert next_auth = @gateway.authorize(@amount, @credit_card, @options)
assert next_auth.authorization
@@ -546,14 +560,34 @@ def test_successful_auth_and_capture_with_gateway_specific_stored_credentials
assert_success capture
end
+ def test_successful_auth_and_capture_with_gateway_specific_recurring_stored_credentials
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST', stored_credential_initiated_reason: 'RECURRING'))
+ assert_success auth
+ assert auth.authorization
+ assert auth.params['scheme_response']
+ assert auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+
+ options = @options.merge(
+ order_id: generate_unique_id,
+ stored_credential_usage: 'USED',
+ stored_credential_initiated_reason: 'RECURRING',
+ stored_credential_transaction_id: auth.params['transaction_identifier']
+ )
+ assert next_auth = @gateway.authorize(@amount, @credit_card, options)
+ assert next_auth.authorization
+ assert next_auth.params['scheme_response']
+ assert next_auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
def test_successful_authorize_with_3ds_with_normalized_stored_credentials
session_id = generate_unique_id
- stored_credential_params = {
- initial_transaction: true,
- reason_type: 'unscheduled',
- initiator: 'merchant',
- network_transaction_id: nil
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
options = @options.merge(
{
execute_threed: true,
@@ -846,32 +880,35 @@ def test_successful_mastercard_credit_on_cft_gateway
assert_equal 'SUCCESS', credit.message
end
- def test_successful_fast_fund_credit_on_cft_gateway
- options = @options.merge({ fast_fund_credit: true })
+ # These three fast_fund_credit tests are currently failing with the message: Disbursement transaction not supported
+ # It seems that the current sandbox setup does not support testing this.
- credit = @cftgateway.credit(@amount, @credit_card, options)
- assert_success credit
- assert_equal 'SUCCESS', credit.message
- end
+ # def test_successful_fast_fund_credit_on_cft_gateway
+ # options = @options.merge({ fast_fund_credit: true })
- def test_successful_fast_fund_credit_with_token_on_cft_gateway
- assert store = @gateway.store(@credit_card, @store_options)
- assert_success store
+ # credit = @cftgateway.credit(@amount, @credit_card, options)
+ # assert_success credit
+ # assert_equal 'SUCCESS', credit.message
+ # end
- options = @options.merge({ fast_fund_credit: true })
- assert credit = @cftgateway.credit(@amount, store.authorization, options)
- assert_success credit
- end
+ # def test_successful_fast_fund_credit_with_token_on_cft_gateway
+ # assert store = @gateway.store(@credit_card, @store_options)
+ # assert_success store
- def test_failed_fast_fund_credit_on_cft_gateway
- options = @options.merge({ fast_fund_credit: true })
- refused_card = credit_card('4444333322221111', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
+ # options = @options.merge({ fast_fund_credit: true })
+ # assert credit = @cftgateway.credit(@amount, store.authorization, options)
+ # assert_success credit
+ # end
- credit = @cftgateway.credit(@amount, refused_card, options)
- assert_failure credit
- assert_equal '01', credit.params['action_code']
- assert_equal "A transaction status of 'ok' or 'PUSH_APPROVED' is required.", credit.message
- end
+ # def test_failed_fast_fund_credit_on_cft_gateway
+ # options = @options.merge({ fast_fund_credit: true })
+ # refused_card = credit_card('4444333322221111', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
+
+ # credit = @cftgateway.credit(@amount, refused_card, options)
+ # assert_failure credit
+ # assert_equal '01', credit.params['action_code']
+ # assert_equal "A transaction status of 'ok' or 'PUSH_APPROVED' is required.", credit.message
+ # end
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@@ -1153,12 +1190,7 @@ def test_failed_refund_synchronous_response
def test_successful_purchase_with_options_synchronous_response
options = @options
- stored_credential_params = {
- initial_transaction: true,
- reason_type: 'unscheduled',
- initiator: 'merchant',
- network_transaction_id: nil
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
options.merge(stored_credential: stored_credential_params)
assert purchase = @cftgateway.purchase(@amount, @credit_card, options.merge(instalments: 3, skip_capture: true, authorization_validated: true))
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index fddad02e088..d7dfe541321 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -330,6 +330,47 @@ def test_authorize_passes_stored_credential_options
assert_success response
end
+ def test_authorize_passes_correct_stored_credential_options_for_first_recurring
+ options = @options.merge(
+ stored_credential_usage: 'FIRST',
+ stored_credential_initiated_reason: 'RECURRING'
+ )
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(//, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_authorize_passes_correct_stored_credential_options_for_used_recurring
+ options = @options.merge(
+ stored_credential_usage: 'USED',
+ stored_credential_initiated_reason: 'RECURRING',
+ stored_credential_transaction_id: '000000000000020005060720116005061'
+ )
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(//, data)
+ assert_match(/000000000000020005060720116005061\<\/schemeTransactionIdentifier\>/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_authorize_passes_correct_stored_credentials_for_first_installment
+ options = @options.merge(
+ stored_credential_usage: 'FIRST',
+ stored_credential_initiated_reason: 'INSTALMENT'
+ )
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(//, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_authorize_passes_sub_merchant_data
options = @options.merge(@sub_merchant_options)
response = stub_comms do
From 2d665562c9ba6aa803e1e3c19554a4b04fbcf0d9 Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Mon, 30 Oct 2023 15:25:06 -0400
Subject: [PATCH 223/390] ecs-3181 preliminary identification of changes
needed, and TODOs to investigate
---
lib/active_merchant/billing/gateways/stripe.rb | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index da0eee50312..9d952019466 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -7,6 +7,7 @@ module Billing #:nodoc:
class StripeGateway < Gateway
self.live_url = 'https://api.stripe.com/v1/'
+ # TODO: Lookup the codes, figure out if I need to add `null` values, and what they map to. May be in StripePaymentIntents Docs
AVS_CODE_TRANSLATOR = {
'line1: pass, zip: pass' => 'Y',
'line1: pass, zip: fail' => 'A',
@@ -17,6 +18,7 @@ class StripeGateway < Gateway
'line1: unchecked, zip: unchecked' => 'I'
}
+ # TODO: Lookup codes, figure out if I need ot add `null` values, and what they map to. May be in StripePaymentIntents Docs
CVC_CODE_TRANSLATOR = {
'pass' => 'M',
'fail' => 'N',
@@ -703,7 +705,7 @@ def commit(method, url, parameters = nil, options = {})
success = success_from(response, options)
card = card_from_response(response)
- avs_code = AVS_CODE_TRANSLATOR["line1: #{card['address_line1_check']}, zip: #{card['address_zip_check']}"]
+ avs_code = AVS_CODE_TRANSLATOR["line1: #{card['address_line1_check']}, zip: #{card['address_zip_check'] || card['address_postal_code_check']}"]
cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']]
Response.new(
success,
@@ -785,7 +787,7 @@ def quickchip_payment?(payment)
end
def card_from_response(response)
- response['card'] || response['active_card'] || response['source'] || {}
+ response['card'] || response['active_card'] || response['source'] || response['checks'] || {}
end
def emv_authorization_from_response(response)
From dc68f7a310c372eb03df86816406d2a11d7cf95a Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Tue, 31 Oct 2023 13:04:24 -0400
Subject: [PATCH 224/390] test change
---
lib/active_merchant/billing/gateways/stripe.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index 9d952019466..0712b4e68cf 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -18,7 +18,7 @@ class StripeGateway < Gateway
'line1: unchecked, zip: unchecked' => 'I'
}
- # TODO: Lookup codes, figure out if I need ot add `null` values, and what they map to. May be in StripePaymentIntents Docs
+ # TODO: Lookup codes, figure out if I need ot add `null` values, and what they map to. May be in StripePaymentIntents Docs
CVC_CODE_TRANSLATOR = {
'pass' => 'M',
'fail' => 'N',
From 36fb171cced4be5dca00523b5e2f8ee6c4dc30c7 Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Tue, 7 Nov 2023 11:14:53 -0500
Subject: [PATCH 225/390] Adding some function and some testing
---
.../billing/gateways/stripe.rb | 21 +++++------
.../remote_stripe_payment_intents_test.rb | 35 +++++++++++++++++++
2 files changed, 46 insertions(+), 10 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index 0712b4e68cf..7cb6f315da0 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -7,18 +7,18 @@ module Billing #:nodoc:
class StripeGateway < Gateway
self.live_url = 'https://api.stripe.com/v1/'
- # TODO: Lookup the codes, figure out if I need to add `null` values, and what they map to. May be in StripePaymentIntents Docs
+ # Docs on AVS codes: https://en.wikipedia.org/w/index.php?title=Address_verification_service&_ga=2.97570079.1027215965.1655989706-2008268124.1655989706#AVS_response_codes
+ # possible response values: https://stripe.com/docs/api/payment_methods/object#payment_method_object-card-checks
AVS_CODE_TRANSLATOR = {
- 'line1: pass, zip: pass' => 'Y',
'line1: pass, zip: fail' => 'A',
'line1: pass, zip: unchecked' => 'B',
- 'line1: fail, zip: pass' => 'Z',
+ 'line1: unchecked, zip: unchecked' => 'I',
'line1: fail, zip: fail' => 'N',
'line1: unchecked, zip: pass' => 'P',
- 'line1: unchecked, zip: unchecked' => 'I'
+ 'line1: pass, zip: pass' => 'Y',
+ 'line1: fail, zip: pass' => 'Z'
}
- # TODO: Lookup codes, figure out if I need ot add `null` values, and what they map to. May be in StripePaymentIntents Docs
CVC_CODE_TRANSLATOR = {
'pass' => 'M',
'fail' => 'N',
@@ -704,9 +704,9 @@ def commit(method, url, parameters = nil, options = {})
response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
success = success_from(response, options)
- card = card_from_response(response)
- avs_code = AVS_CODE_TRANSLATOR["line1: #{card['address_line1_check']}, zip: #{card['address_zip_check'] || card['address_postal_code_check']}"]
- cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']]
+ card_checks = card_from_response(response)
+ avs_code = AVS_CODE_TRANSLATOR["line1: #{card_checks['address_line1_check']}, zip: #{card_checks['address_zip_check'] || card_checks['address_postal_code_check']}"]
+ cvc_code = CVC_CODE_TRANSLATOR[card_checks['cvc_check']]
Response.new(
success,
message_from(success, response),
@@ -786,8 +786,9 @@ def quickchip_payment?(payment)
payment.respond_to?(:read_method) && payment.read_method == 'contact_quickchip'
end
- def card_from_response(response)
- response['card'] || response['active_card'] || response['source'] || response['checks'] || {}
+ def card_from_response(response)
+ # StripePI puts the AVS and CVC check significantly deeper into the response object
+ response['card'] || response['active_card'] || response['source'] || response.dig('charges','data',0,'payment_method_details','card','checks') || {}
end
def emv_authorization_from_response(response)
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 14811db6d95..ec51cc8d216 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -10,6 +10,8 @@ def setup
@declined_payment_method = 'pm_card_chargeDeclined'
@three_ds_moto_enabled = 'pm_card_authenticationRequiredOnSetup'
@three_ds_authentication_required = 'pm_card_authenticationRequired'
+ @cvc_check_fails_credit_card = 'pm_card_cvcCheckFail'
+ @avs_fail_card = 'pm_card_avsFail'
@three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession'
@three_ds_off_session_credit_card = credit_card(
'4000002500003155',
@@ -17,30 +19,35 @@ def setup
month: 10,
year: 2028
)
+
@three_ds_1_credit_card = credit_card(
'4000000000003063',
verification_value: '737',
month: 10,
year: 2028
)
+
@three_ds_credit_card = credit_card(
'4000000000003220',
verification_value: '737',
month: 10,
year: 2028
)
+
@three_ds_not_required_card = credit_card(
'4000000000003055',
verification_value: '737',
month: 10,
year: 2028
)
+
@three_ds_external_data_card = credit_card(
'4000002760003184',
verification_value: '737',
month: 10,
year: 2031
)
+
@visa_card = credit_card(
'4242424242424242',
verification_value: '737',
@@ -1487,4 +1494,32 @@ def test_transcript_scrubbing
assert_scrubbed(@three_ds_credit_card.verification_value, transcript)
assert_scrubbed(@gateway.options[:login], transcript)
end
+
+ def test_succeeded_cvc_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @visa_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'M', purchase.cvv_result.dig('code')
+ assert_equal 'CVV matches', purchase.cvv_result.dig('message')
+ end
+
+ def test_failed_cvc_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @cvc_check_fails_credit_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'N', purchase.cvv_result.dig('code')
+ assert_equal 'CVV does not match', purchase.cvv_result.dig('message')
+ end
+
+ def test_failed_avs_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @avs_fail_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'N', purchase.avs_result['code']
+ assert_equal 'N', purchase.avs_result['postal_match']
+ assert_equal 'N', purchase.avs_result['street_match']
+ end
end
From e9a4940462d4653d80d386f828801f8145e569dc Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Tue, 7 Nov 2023 12:45:51 -0500
Subject: [PATCH 226/390] linting fix
---
lib/active_merchant/billing/gateways/stripe.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index 7cb6f315da0..4408b7e3c4d 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -786,9 +786,9 @@ def quickchip_payment?(payment)
payment.respond_to?(:read_method) && payment.read_method == 'contact_quickchip'
end
- def card_from_response(response)
+ def card_from_response(response)
# StripePI puts the AVS and CVC check significantly deeper into the response object
- response['card'] || response['active_card'] || response['source'] || response.dig('charges','data',0,'payment_method_details','card','checks') || {}
+ response['card'] || response['active_card'] || response['source'] || response.dig('charges', 'data', 0, 'payment_method_details', 'card', 'checks') || {}
end
def emv_authorization_from_response(response)
From 9cf4fcaef0995bccdb1c225ad1c563dfbf4418d7 Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Tue, 7 Nov 2023 13:08:10 -0500
Subject: [PATCH 227/390] other lint fix
---
test/remote/gateways/remote_stripe_payment_intents_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index ec51cc8d216..c4b1267d382 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -1498,7 +1498,7 @@ def test_transcript_scrubbing
def test_succeeded_cvc_check
options = {}
assert purchase = @gateway.purchase(@amount, @visa_card, options)
-
+
assert_equal 'succeeded', purchase.params['status']
assert_equal 'M', purchase.cvv_result.dig('code')
assert_equal 'CVV matches', purchase.cvv_result.dig('message')
From b8db16aaf26aba218c6e8ca12ace7aae1b7fc03d Mon Sep 17 00:00:00 2001
From: Brad Broge
Date: Thu, 9 Nov 2023 13:15:41 -0500
Subject: [PATCH 228/390] adding unit test
---
.../gateways/stripe_payment_intents_test.rb | 110 ++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 175b6d16211..610e7542d28 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -887,6 +887,17 @@ def test_succesful_purchase_with_mit_unscheduled
end.respond_with(successful_create_intent_response)
end
+ def test_successful_avs_and_cvc_check
+ @gateway.expects(:ssl_request).returns(successful_purchase_avs_pass)
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @visa_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'M', purchase.cvv_result.dig('code')
+ assert_equal 'CVV matches', purchase.cvv_result.dig('message')
+ assert_equal 'Y', purchase.avs_result.dig('code')
+ end
+
private
def successful_setup_purchase
@@ -2077,4 +2088,103 @@ def scrubbed
Conn close
SCRUBBED
end
+
+ def successful_purchase_avs_pass
+ <<-RESPONSE
+ {
+ "id": "pi_3OAbBTAWOtgoysog36MuKzzw",
+ "object": "payment_intent",
+ "amount": 2000,
+ "amount_capturable": 0,
+ "amount_received": 2000,
+ "capture_method": "automatic",
+ "charges": {
+ "object": "list",
+ "data": [
+ {
+ "id": "ch_3OAbBTAWOtgoysog3eoQxrT9",
+ "object": "charge",
+ "amount": 2000,
+ "amount_captured": 2000,
+ "outcome": {
+ "network_status": "approved_by_network",
+ "reason": null,
+ "risk_level": "normal",
+ "risk_score": 37,
+ "seller_message": "Payment complete.",
+ "type": "authorized"
+ },
+ "paid": true,
+ "payment_intent": "pi_3OAbBTAWOtgoysog36MuKzzw",
+ "payment_method": "pm_1OAbBTAWOtgoysogVf7KTk4H",
+ "payment_method_details": {
+ "card": {
+ "amount_authorized": 2000,
+ "brand": "visa",
+ "checks": {
+ "address_line1_check": "pass",
+ "address_postal_code_check": "pass",
+ "cvc_check": "pass"
+ },
+ "country": "US",
+ "ds_transaction_id": null,
+ "exp_month": 10,
+ "exp_year": 2028,
+ "extended_authorization": {
+ "status": "disabled"
+ },
+ "fingerprint": "hfaVNMiXc0dYSiC5",
+ "funding": "credit",
+ "incremental_authorization": {
+ "status": "unavailable"
+ },
+ "installments": null,
+ "last4": "4242",
+ "mandate": null,
+ "moto": null,
+ "multicapture": {
+ "status": "unavailable"
+ },
+ "network": "visa",
+ "network_token": {
+ "used": false
+ },
+ "network_transaction_id": "104102978678771",
+ "overcapture": {
+ "maximum_amount_capturable": 2000,
+ "status": "unavailable"
+ },
+ "three_d_secure": null,
+ "wallet": null
+ },
+ "type": "card"
+ },
+ "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKJCUtKoGMgYHwo4IbXs6LBbLMStawAC9eTsIUAmLDXw4dZNPmxzC6ds3zZxb-WVIVBJi_F4M59cPA3fR",
+ "refunded": false,
+ "refunds": {
+ "object": "list",
+ "data": [],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/charges/ch_3OAbBTAWOtgoysog3eoQxrT9/refunds"
+ }
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/charges?payment_intent=pi_3OAbBTAWOtgoysog36MuKzzw"
+ },
+ "client_secret": "pi_3OAbBTAWOtgoysog36MuKzzw_secret_YjUUEVStFrCFJK0imrUjspILY",
+ "confirmation_method": "automatic",
+ "created": 1699547663,
+ "currency": "usd",
+ "latest_charge": "ch_3OAbBTAWOtgoysog3eoQxrT9",
+ "payment_method": "pm_1OAbBTAWOtgoysogVf7KTk4H",
+ "payment_method_types": [
+ "card"
+ ],
+ "status": "succeeded"
+ }
+ RESPONSE
+ end
end
From 376545a91f7b2b2ba5eef3b2b9a49510acad96fc Mon Sep 17 00:00:00 2001
From: Dustin A Haefele <45601251+DustinHaefele@users.noreply.github.com>
Date: Wed, 15 Nov 2023 11:02:48 -0500
Subject: [PATCH 229/390] Vantiv Express: New Xml gateway (#4956)
Remote tests:
32 tests, 93 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit tests:
32 tests, 189 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Co-authored-by: Alma Malambo
---------
Co-authored-by: Alma Malambo
---
CHANGELOG | 2 +
.../billing/gateways/vantiv_express.rb | 583 +++++++++++++++++
.../gateways/remote_vantiv_express_test.rb | 367 +++++++++++
test/unit/gateways/vantiv_express_test.rb | 614 ++++++++++++++++++
4 files changed, 1566 insertions(+)
create mode 100644 lib/active_merchant/billing/gateways/vantiv_express.rb
create mode 100644 test/remote/gateways/remote_vantiv_express_test.rb
create mode 100644 test/unit/gateways/vantiv_express_test.rb
diff --git a/CHANGELOG b/CHANGELOG
index e6e7edfc423..f12bbbb7033 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -54,6 +54,8 @@
* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
* Redsys Rest: Add support for new gateway type Redsys Rest [aenand] #4951
* CyberSource: surface the reconciliationID2 field [yunnydang] #4934
+* Worldpay: Update stored credentials logic [DustinHaefele] #4950
+* Vantiv Express: New Xml gateway [DustinHaefele] #4956
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/vantiv_express.rb b/lib/active_merchant/billing/gateways/vantiv_express.rb
new file mode 100644
index 00000000000..bb73051e680
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/vantiv_express.rb
@@ -0,0 +1,583 @@
+require 'nokogiri'
+require 'securerandom'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class VantivExpressGateway < Gateway
+ self.test_url = 'https://certtransaction.elementexpress.com'
+ self.live_url = 'https://transaction.elementexpress.com'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[visa master american_express discover diners_club jcb]
+
+ self.homepage_url = 'http://www.elementps.com'
+ self.display_name = 'Element'
+
+ SERVICE_TEST_URL = 'https://certservices.elementexpress.com'
+ SERVICE_LIVE_URL = 'https://services.elementexpress.com'
+
+ NETWORK_TOKEN_TYPE = {
+ apple_pay: 2,
+ google_pay: 1
+ }
+
+ CARD_PRESENT_CODE = {
+ 'Unknown' => 1,
+ 'Present' => 2,
+ 'NotPresent' => 3
+ }
+
+ MARKET_CODE = {
+ 'AutoRental' => 1,
+ 'DirectMarketing' => 2,
+ 'ECommerce' => 3,
+ 'FoodRestaurant' => 4,
+ 'HotelLodging' => 5,
+ 'Petroleum' => 6,
+ 'Retail' => 7,
+ 'QSR' => 8,
+ 'Grocery' => 9
+ }
+
+ PAYMENT_TYPE = {
+ 'NotUsed' => 0,
+ 'Recurring' => 1,
+ 'Installment' => 2,
+ 'CardHolderInitiated' => 3,
+ 'CredentialOnFile' => 4
+ }
+
+ REVERSAL_TYPE = {
+ 'System' => 0,
+ 'Full' => 1,
+ 'Partial' => 2
+ }
+
+ SUBMISSION_TYPE = {
+ 'NotUsed' => 0,
+ 'Initial' => 1,
+ 'Subsequent' => 2,
+ 'Resubmission' => 3,
+ 'ReAuthorization' => 4,
+ 'DelayedCharges' => 5,
+ 'NoShow' => 6
+ }
+
+ LODGING_PPC = {
+ 'NonParticipant' => 0,
+ 'DollarLimit500' => 1,
+ 'DollarLimit1000' => 2,
+ 'DollarLimit1500' => 3
+ }
+
+ LODGING_SPC = {
+ 'Default' => 0,
+ 'Sale' => 1,
+ 'NoShow' => 2,
+ 'AdvanceDeposit' => 3
+ }
+
+ LODGING_CHARGE_TYPE = {
+ 'Default' => 0,
+ 'Restaurant' => 1,
+ 'GiftShop' => 2
+ }
+
+ TERMINAL_TYPE = {
+ 'Unknown' => 0,
+ 'PointOfSale' => 1,
+ 'ECommerce' => 2,
+ 'MOTO' => 3,
+ 'FuelPump' => 4,
+ 'ATM' => 5,
+ 'Voice' => 6,
+ 'Mobile' => 7,
+ 'WebSiteGiftCard' => 8
+ }
+
+ CARD_HOLDER_PRESENT_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'Present' => 2,
+ 'NotPresent' => 3,
+ 'MailOrder' => 4,
+ 'PhoneOrder' => 5,
+ 'StandingAuth' => 6,
+ 'ECommerce' => 7
+ }
+
+ CARD_INPUT_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'MagstripeRead' => 2,
+ 'ContactlessMagstripeRead' => 3,
+ 'ManualKeyed' => 4,
+ 'ManualKeyedMagstripeFailure' => 5,
+ 'ChipRead' => 6,
+ 'ContactlessChipRead' => 7,
+ 'ManualKeyedChipReadFailure' => 8,
+ 'MagstripeReadChipReadFailure' => 9,
+ 'MagstripeReadNonTechnicalFallback' => 10
+ }
+
+ CVV_PRESENCE_CODE = {
+ 'UseDefault' => 0,
+ 'NotProvided' => 1,
+ 'Provided' => 2,
+ 'Illegible' => 3,
+ 'CustomerIllegible' => 4
+ }
+
+ TERMINAL_CAPABILITY_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'NoTerminal' => 2,
+ 'MagstripeReader' => 3,
+ 'ContactlessMagstripeReader' => 4,
+ 'KeyEntered' => 5,
+ 'ChipReader' => 6,
+ 'ContactlessChipReader' => 7
+ }
+
+ TERMINAL_ENVIRONMENT_CODE = {
+ 'Default' => 0,
+ 'NoTerminal' => 1,
+ 'LocalAttended' => 2,
+ 'LocalUnattended' => 3,
+ 'RemoteAttended' => 4,
+ 'RemoteUnattended' => 5,
+ 'ECommerce' => 6
+ }
+
+ def initialize(options = {})
+ requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ action = payment.is_a?(Check) ? 'CheckSale' : 'CreditCardSale'
+ eci = payment.is_a?(NetworkTokenizationCreditCard) ? parse_eci(payment) : nil
+
+ request = build_xml_request do |xml|
+ xml.send(action, xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ add_lodging(xml, options)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def authorize(money, payment, options = {})
+ eci = payment.is_a?(NetworkTokenizationCreditCard) ? parse_eci(payment) : nil
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAuthorization(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ add_lodging(xml, options)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def capture(money, authorization, options = {})
+ trans_id, _, eci = authorization.split('|')
+ options[:trans_id] = trans_id
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAuthorizationCompletion(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money)
+ end
+
+ def refund(money, authorization, options = {})
+ trans_id, _, eci = authorization.split('|')
+ options[:trans_id] = trans_id
+
+ request = build_xml_request do |xml|
+ xml.CreditCardReturn(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money)
+ end
+
+ def credit(money, payment, options = {})
+ eci = payment.is_a?(NetworkTokenizationCreditCard) ? parse_eci(payment) : nil
+
+ request = build_xml_request do |xml|
+ xml.CreditCardCredit(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def void(authorization, options = {})
+ trans_id, trans_amount, eci = authorization.split('|')
+ options.merge!({ trans_id: trans_id, trans_amount: trans_amount, reversal_type: 1 })
+
+ request = build_xml_request do |xml|
+ xml.CreditCardReversal(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, trans_amount, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, trans_amount)
+ end
+
+ def store(payment, options = {})
+ request = build_xml_request do |xml|
+ xml.PaymentAccountCreate(xmlns: SERVICE_LIVE_URL) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_payment_account(xml, payment, options[:payment_account_reference_number] || SecureRandom.hex(20))
+ add_address(xml, options)
+ end
+ end
+
+ commit(request, payment, nil, :store)
+ end
+
+ def verify(payment, options = {})
+ eci = payment.is_a?(NetworkTokenizationCreditCard) ? parse_eci(payment) : nil
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAVSOnly(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, 0, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ end
+ end
+
+ commit(request)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_credentials(xml)
+ xml.Credentials do
+ xml.AccountID @options[:account_id]
+ xml.AccountToken @options[:account_token]
+ xml.AcceptorID @options[:acceptor_id]
+ end
+ xml.Application do
+ xml.ApplicationID @options[:application_id]
+ xml.ApplicationName @options[:application_name]
+ xml.ApplicationVersion @options[:application_version]
+ end
+ end
+
+ def add_payment_method(xml, payment)
+ if payment.is_a?(String)
+ add_payment_account_id(xml, payment)
+ elsif payment.is_a?(Check)
+ add_echeck(xml, payment)
+ elsif payment.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_card(xml, payment)
+ else
+ add_credit_card(xml, payment)
+ end
+ end
+
+ def add_payment_account(xml, payment, payment_account_reference_number)
+ xml.PaymentAccount do
+ xml.PaymentAccountType payment_account_type(payment)
+ xml.PaymentAccountReferenceNumber payment_account_reference_number
+ end
+ end
+
+ def add_payment_account_id(xml, payment)
+ xml.PaymentAccount do
+ xml.PaymentAccountID payment
+ end
+ end
+
+ def add_transaction(xml, money, options = {}, network_token_eci = nil)
+ xml.Transaction do
+ xml.ReversalType REVERSAL_TYPE[options[:reversal_type]] || options[:reversal_type] if options[:reversal_type]
+ xml.TransactionID options[:trans_id] if options[:trans_id]
+ xml.TransactionAmount amount(money.to_i) if money
+ xml.MarketCode market_code(money, options, network_token_eci) if options[:market_code] || money
+ xml.ReferenceNumber options[:order_id].present? ? options[:order_id][0, 50] : SecureRandom.hex(20)
+ xml.TicketNumber options[:ticket_number] || rand(1..999999)
+ xml.MerchantSuppliedTransactionID options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
+ xml.PaymentType PAYMENT_TYPE[options[:payment_type]] || options[:payment_type] if options[:payment_type]
+ xml.SubmissionType SUBMISSION_TYPE[options[:submission_type]] || options[:submission_type] if options[:submission_type]
+ xml.DuplicateCheckDisableFlag 1 if options[:duplicate_check_disable_flag].to_s == 'true' || options[:duplicate_override_flag].to_s == 'true'
+ end
+ end
+
+ def parse_eci(payment)
+ eci = payment.eci
+ eci[0] == '0' ? eci.sub!(/^0/, '') : eci
+ end
+
+ def market_code(money, options, network_token_eci)
+ return 3 if network_token_eci
+
+ MARKET_CODE[options[:market_code]] || options[:market_code] || 0
+ end
+
+ def add_lodging(xml, options)
+ if options[:lodging]
+ lodging = parse_lodging(options[:lodging])
+ xml.ExtendedParameters do
+ xml.Lodging do
+ xml.LodgingAgreementNumber lodging[:agreement_number] if lodging[:agreement_number]
+ xml.LodgingCheckInDate lodging[:check_in_date] if lodging[:check_in_date]
+ xml.LodgingCheckOutDate lodging[:check_out_date] if lodging[:check_out_date]
+ xml.LodgingRoomAmount lodging[:room_amount] if lodging[:room_amount]
+ xml.LodgingRoomTax lodging[:room_tax] if lodging[:room_tax]
+ xml.LodgingNoShowIndicator lodging[:no_show_indicator] if lodging[:no_show_indicator]
+ xml.LodgingDuration lodging[:duration] if lodging[:duration]
+ xml.LodgingCustomerName lodging[:customer_name] if lodging[:customer_name]
+ xml.LodgingClientCode lodging[:client_code] if lodging[:client_code]
+ xml.LodgingExtraChargesDetail lodging[:extra_charges_detail] if lodging[:extra_charges_detail]
+ xml.LodgingExtraChargesAmounts lodging[:extra_charges_amounts] if lodging[:extra_charges_amounts]
+ xml.LodgingPrestigiousPropertyCode lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ xml.LodgingSpecialProgramCode lodging[:special_program_code] if lodging[:special_program_code]
+ xml.LodgingChargeType lodging[:charge_type] if lodging[:charge_type]
+ end
+ end
+ end
+ end
+
+ def add_terminal(xml, options, network_token_eci = nil)
+ options = parse_terminal(options)
+
+ xml.Terminal do
+ xml.TerminalID options[:terminal_id] || '01'
+ xml.TerminalType options[:terminal_type] if options[:terminal_type]
+ xml.CardPresentCode options[:card_present_code] || 0
+ xml.CardholderPresentCode options[:card_holder_present_code] || 0
+ xml.CardInputCode options[:card_input_code] || 0
+ xml.CVVPresenceCode options[:cvv_presence_code] || 0
+ xml.TerminalCapabilityCode options[:terminal_capability_code] || 0
+ xml.TerminalEnvironmentCode options[:terminal_environment_code] || 0
+ xml.MotoECICode network_token_eci || 7
+ xml.PartialApprovedFlag options[:partial_approved_flag] if options[:partial_approved_flag]
+ end
+ end
+
+ def add_credit_card(xml, payment)
+ xml.Card do
+ xml.CardNumber payment.number
+ xml.ExpirationMonth format(payment.month, :two_digits)
+ xml.ExpirationYear format(payment.year, :two_digits)
+ xml.CardholderName "#{payment.first_name} #{payment.last_name}"
+ xml.CVV payment.verification_value
+ end
+ end
+
+ def add_echeck(xml, payment)
+ xml.DemandDepositAccount do
+ xml.AccountNumber payment.account_number
+ xml.RoutingNumber payment.routing_number
+ xml.DDAAccountType payment.account_type == 'checking' ? 0 : 1
+ end
+ end
+
+ def add_network_tokenization_card(xml, payment)
+ xml.Card do
+ xml.CardNumber payment.number
+ xml.ExpirationMonth format(payment.month, :two_digits)
+ xml.ExpirationYear format(payment.year, :two_digits)
+ xml.CardholderName "#{payment.first_name} #{payment.last_name}"
+ xml.Cryptogram payment.payment_cryptogram
+ xml.WalletType NETWORK_TOKEN_TYPE[payment.source]
+ end
+ end
+
+ def add_address(xml, options)
+ address = address = options[:billing_address] || options[:address]
+ shipping_address = options[:shipping_address]
+
+ if address || shipping_address
+ xml.Address do
+ if address
+ address[:email] ||= options[:email]
+
+ xml.BillingAddress1 address[:address1] if address[:address1]
+ xml.BillingAddress2 address[:address2] if address[:address2]
+ xml.BillingCity address[:city] if address[:city]
+ xml.BillingState address[:state] if address[:state]
+ xml.BillingZipcode address[:zip] if address[:zip]
+ xml.BillingEmail address[:email] if address[:email]
+ xml.BillingPhone address[:phone_number] if address[:phone_number]
+ end
+
+ if shipping_address
+ xml.ShippingAddress1 shipping_address[:address1] if shipping_address[:address1]
+ xml.ShippingAddress2 shipping_address[:address2] if shipping_address[:address2]
+ xml.ShippingCity shipping_address[:city] if shipping_address[:city]
+ xml.ShippingState shipping_address[:state] if shipping_address[:state]
+ xml.ShippingZipcode shipping_address[:zip] if shipping_address[:zip]
+ xml.ShippingEmail shipping_address[:email] if shipping_address[:email]
+ xml.ShippingPhone shipping_address[:phone_number] if shipping_address[:phone_number]
+ end
+ end
+ end
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.remove_namespaces!
+ root = doc.root.xpath('//response/*')
+
+ root = doc.root.xpath('//Response/*') if root.empty?
+
+ root.each do |node|
+ if node.elements.empty?
+ response[node.name.downcase] = node.text
+ else
+ node_name = node.name.downcase
+ response[node_name] = {}
+
+ node.elements.each do |childnode|
+ response[node_name][childnode.name.downcase] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def parse_lodging(lodging)
+ lodging[:prestigious_property_code] = LODGING_PPC[lodging[:prestigious_property_code]] || lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ lodging[:special_program_code] = LODGING_SPC[lodging[:special_program_code]] || lodging[:special_program_code] if lodging[:special_program_code]
+ lodging[:charge_type] = LODGING_CHARGE_TYPE[lodging[:charge_type]] || lodging[:charge_type] if lodging[:charge_type]
+
+ lodging
+ end
+
+ def parse_terminal(options)
+ options[:terminal_type] = TERMINAL_TYPE[options[:terminal_type]] || options[:terminal_type]
+ options[:card_present_code] = CARD_PRESENT_CODE[options[:card_present_code]] || options[:card_present_code]
+ options[:card_holder_present_code] = CARD_HOLDER_PRESENT_CODE[options[:card_holder_present_code]] || options[:card_holder_present_code]
+ options[:card_input_code] = CARD_INPUT_CODE[options[:card_input_code]] || options[:card_input_code]
+ options[:cvv_presence_code] = CVV_PRESENCE_CODE[options[:cvv_presence_code]] || options[:cvv_presence_code]
+ options[:terminal_capability_code] = TERMINAL_CAPABILITY_CODE[options[:terminal_capability_code]] || options[:terminal_capability_code]
+ options[:terminal_environment_code] = TERMINAL_ENVIRONMENT_CODE[options[:terminal_environment_code]] || options[:terminal_environment_code]
+
+ options
+ end
+
+ def commit(xml, amount = nil, payment = nil, action = nil)
+ response = parse(ssl_post(url(action), xml, headers))
+ success = success_from(response)
+
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response, amount, payment),
+ avs_result: success ? avs_from(response) : nil,
+ cvv_result: success ? cvv_from(response) : nil,
+ test: test?
+ )
+ end
+
+ def authorization_from(action, response, amount, payment)
+ return response.dig('paymentaccount', 'paymentaccountid') if action == :store
+
+ if response['transaction']
+ authorization = "#{response.dig('transaction', 'transactionid')}|#{amount}"
+ authorization << "|#{parse_eci(payment)}" if payment.is_a?(NetworkTokenizationCreditCard)
+ authorization
+ end
+ end
+
+ def success_from(response)
+ response['expressresponsecode'] == '0'
+ end
+
+ def message_from(response)
+ response['expressresponsemessage']
+ end
+
+ def avs_from(response)
+ AVSResult.new(code: response['card']['avsresponsecode']) if response['card']
+ end
+
+ def cvv_from(response)
+ CVVResult.new(response['card']['cvvresponsecode']) if response['card']
+ end
+
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ yield(xml)
+ end
+
+ builder.to_xml
+ end
+
+ def payment_account_type(payment)
+ return 0 unless payment.is_a?(Check)
+
+ if payment.account_type == 'checking'
+ 1
+ elsif payment.account_type == 'savings'
+ 2
+ else
+ 3
+ end
+ end
+
+ def url(action)
+ if action == :store
+ test? ? SERVICE_TEST_URL : SERVICE_LIVE_URL
+ else
+ test? ? test_url : live_url
+ end
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'text/xml'
+ }
+ end
+ end
+ end
+end
diff --git a/test/remote/gateways/remote_vantiv_express_test.rb b/test/remote/gateways/remote_vantiv_express_test.rb
new file mode 100644
index 00000000000..93430ecf1f7
--- /dev/null
+++ b/test/remote/gateways/remote_vantiv_express_test.rb
@@ -0,0 +1,367 @@
+require 'test_helper'
+
+class RemoteVantivExpressTest < Test::Unit::TestCase
+ def setup
+ @gateway = VantivExpressGateway.new(fixtures(:element))
+
+ @amount = rand(1000..2000)
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('6060704495764400')
+ @check = check
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+
+ @google_pay_network_token = network_tokenization_credit_card(
+ '6011000400000000',
+ month: '01',
+ year: Time.new.year + 2,
+ first_name: 'Jane',
+ last_name: 'Doe',
+ verification_value: '888',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ eci: '05',
+ transaction_id: '123456789',
+ source: :google_pay
+ )
+
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
+ month: '10',
+ year: Time.new.year + 2,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ payment_cryptogram: 'CeABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ eci: '05',
+ transaction_id: 'abc123',
+ source: :apple_pay
+ )
+ end
+
+ def test_successful_purchase_and_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_failed_purchase
+ @amount = 20
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CARD INFO', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_payment_account_token
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_shipping_address
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: address(address1: 'Shipping')))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_billing_email
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(email: 'test@example.com'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_card_present_code_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(card_present_code: 'Present'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_payment_type_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_submission_type_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(submission_type: 'NotUsed'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_duplicate_check_disable_flag
+ amount = @amount
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: true))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'true'))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_duplicate_override_flag
+ amount = @amount
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: true))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: 'true'))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_terminal_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(terminal_id: '02'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_lodging_and_all_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'AdvanceDeposit',
+ charge_type: 'Restaurant'
+ },
+ card_holder_present_code: '2',
+ card_input_code: '4',
+ card_present_code: 'NotPresent',
+ cvv_presence_code: '2',
+ market_code: 'HotelLodging',
+ terminal_capability_code: 'ChipReader',
+ terminal_environment_code: 'LocalUnattended',
+ terminal_type: 'Mobile',
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_enum_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 1,
+ special_program_code: 2,
+ charge_type: 2
+ },
+ card_holder_present_code: '2',
+ card_input_code: '4',
+ card_present_code: 0,
+ cvv_presence_code: 2,
+ market_code: 5,
+ terminal_capability_code: 5,
+ terminal_environment_code: 6,
+ terminal_type: 2,
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_google_pay
+ response = @gateway.purchase(@amount, @google_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = @gateway.purchase(@amount, @apple_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize_capture_and_void_with_apple_pay
+ auth = @gateway.authorize(3100, @apple_pay_network_token, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(3200, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_verify_with_apple_pay
+ response = @gateway.verify(@apple_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ @amount = 20
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CARD INFO', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_successful_credit
+ credit_options = @options.merge({ ticket_number: '1', market_code: 'FoodRestaurant', merchant_supplied_transaction_id: '123' })
+ credit = @gateway.credit(@amount, @credit_card, credit_options)
+
+ assert_success credit
+ end
+
+ def test_failed_credit
+ credit = @gateway.credit(nil, @credit_card, @options)
+
+ assert_failure credit
+ assert_equal 'TransactionAmount required', credit.message
+ end
+
+ def test_successful_partial_capture_and_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_success capture
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'TransactionAmount required', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_match %r{PaymentAccount created}, response.message
+ end
+
+ def test_invalid_login
+ gateway = ElementGateway.new(account_id: '3', account_token: '3', application_id: '3', acceptor_id: '3', application_name: '3', application_version: '3')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid AccountToken}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:account_token], transcript)
+ end
+
+ def test_transcript_scrubbing_with_echeck
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:account_token], transcript)
+ end
+end
diff --git a/test/unit/gateways/vantiv_express_test.rb b/test/unit/gateways/vantiv_express_test.rb
new file mode 100644
index 00000000000..8b208538a95
--- /dev/null
+++ b/test/unit/gateways/vantiv_express_test.rb
@@ -0,0 +1,614 @@
+require 'test_helper'
+
+class VantivExpressTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = VantivExpressGateway.new(fixtures(:element))
+ @credit_card = credit_card
+ @check = check
+ @amount = 100
+
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
+ month: '10',
+ year: Time.new.year + 2,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ payment_cryptogram: 'CeABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ eci: '07',
+ transaction_id: 'abc123',
+ source: :apple_pay
+ )
+
+ @google_pay_network_token = network_tokenization_credit_card(
+ '6011000400000000',
+ month: '01',
+ year: Time.new.year + 2,
+ first_name: 'Jane',
+ last_name: 'Doe',
+ verification_value: '888',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ eci: '05',
+ transaction_id: '123456789',
+ source: :google_pay
+ )
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert_equal '2005831886|100', response.authorization
+ end
+
+ def test_successful_purchase_without_name
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ @credit_card.first_name = nil
+ @credit_card.last_name = nil
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert_equal '2005831886|100', response.authorization
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_echeck
+ @gateway.expects(:ssl_post).returns(successful_purchase_with_echeck_response)
+
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+
+ assert_equal '2005838412|100', response.authorization
+ end
+
+ def test_failed_purchase_with_echeck
+ @gateway.expects(:ssl_post).returns(failed_purchase_with_echeck_response)
+
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = stub_comms do
+ @gateway.purchase(@amount, @apple_pay_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '2', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_google_pay
+ response = stub_comms do
+ @gateway.purchase(@amount, @google_pay_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_payment_account_token
+ @gateway.expects(:ssl_post).returns(successful_purchase_with_payment_account_token_response)
+
+ response = @gateway.purchase(@amount, 'payment-account-token-id', @options)
+ assert_success response
+
+ assert_equal '2005838405|100', response.authorization
+ end
+
+ def test_failed_purchase_with_payment_account_token
+ @gateway.expects(:ssl_post).returns(failed_purchase_with_payment_account_token_response)
+
+ response = @gateway.purchase(@amount, 'bad-payment-account-token-id', @options)
+ assert_failure response
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal '2005832533|100', response.authorization
+ end
+
+ def test_failed_authorize
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+
+ response = @gateway.capture(@amount, 'trans-id')
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_post).returns(failed_capture_response)
+
+ response = @gateway.capture(@amount, 'bad-trans-id')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+
+ response = @gateway.refund(@amount, 'trans-id')
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+
+ response = @gateway.refund(@amount, 'bad-trans-id')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+
+ response = @gateway.void('trans-id')
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_post).returns(failed_void_response)
+
+ response = @gateway.void('bad-trans-id')
+ assert_failure response
+ assert_equal 'TransactionAmount required', response.message
+ end
+
+ def test_successful_verify
+ @gateway.expects(:ssl_post).returns(successful_verify_response)
+
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_handles_error_response
+ @gateway.expects(:ssl_post).returns(error_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal response.message, 'TargetNamespace required'
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_card_present_code
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(card_present_code: 'Present'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '2', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_element_string_lodging_fields
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(lodging: lodging_fields))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match "#{lodging_fields[:agreement_number]}", data
+ assert_match "#{lodging_fields[:check_in_date]}", data
+ assert_match "#{lodging_fields[:check_out_date]}", data
+ assert_match "#{lodging_fields[:room_amount]}", data
+ assert_match "#{lodging_fields[:room_tax]}", data
+ assert_match "#{lodging_fields[:no_show_indicator]}", data
+ assert_match "#{lodging_fields[:duration]}", data
+ assert_match "#{lodging_fields[:customer_name]}", data
+ assert_match "#{lodging_fields[:client_code]}", data
+ assert_match "#{lodging_fields[:extra_charges_detail]}", data
+ assert_match "#{lodging_fields[:extra_charges_amounts]}", data
+ assert_match '1', data
+ assert_match '3', data
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_element_enum_lodging_fields
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(lodging: enum_lodging_fields))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ assert_match '3', data
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_element_string_terminal_fields
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(terminal_fields))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '02', data
+ assert_match '0', data
+ assert_match '1', data
+ assert_match '0', data
+ assert_match '4', data
+ assert_match '3', data
+ assert_match '3', data
+ assert_match '2', data
+ assert_match '7', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_enum_terminal_fields
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(enum_terminal_fields))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '02', data
+ assert_match '0', data
+ assert_match '0', data
+ assert_match '0', data
+ assert_match '4', data
+ assert_match '3', data
+ assert_match '3', data
+ assert_match '2', data
+ assert_match '7', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_payment_type
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '0', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_submission_type
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(submission_type: 'NotUsed'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '0', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_duplicate_check_disable_flag
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'true'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: false))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'xxx'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'False'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ # when duplicate_check_disable_flag is NOT passed, should not be in XML at all
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match %r(False), data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_duplicate_override_flag
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'true'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '1', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: false))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'False'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match 'False', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ # when duplicate_override_flag is NOT passed, should not be in XML at all
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_not_match %r(False), data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_terminal_id
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(terminal_id: '02'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '02', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_billing_email
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(email: 'test@example.com'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match 'test@example.com', data
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_credit_with_extra_fields
+ credit_options = @options.merge({ ticket_number: '1', market_code: 'FoodRestaurant', merchant_supplied_transaction_id: '123' })
+ stub_comms do
+ @gateway.credit(@amount, @credit_card, credit_options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '14', data
+ assert_match '123', data
+ end.respond_with(successful_credit_response)
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ private
+
+ def lodging_fields
+ {
+ agreement_number: '5a43d41dc251949cc3395542',
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'AdvanceDeposit',
+ charge_type: 'Restaurant'
+ }
+ end
+
+ def enum_lodging_fields
+ {
+ prestigious_property_code: 1,
+ special_program_code: 3,
+ charge_type: 1
+ }
+ end
+
+ def terminal_fields
+ {
+ terminal_id: '02',
+ terminal_type: 'Unknown',
+ card_present_code: 'Unknown',
+ card_holder_present_code: 'Default',
+ card_input_code: 'ManualKeyed',
+ cvv_presence_code: 'Illegible',
+ terminal_capability_code: 'MagstripeReader',
+ terminal_environment_code: 'LocalAttended'
+ }
+ end
+
+ def enum_terminal_fields
+ {
+ terminal_id: '02',
+ terminal_type: 0,
+ card_present_code: 0,
+ card_holder_present_code: 0,
+ card_input_code: 4,
+ cvv_presence_code: 3,
+ terminal_capability_code: 3,
+ terminal_environment_code: 2
+ }
+ end
+
+ def pre_scrubbed
+ <<~XML
+ \n\n \n 1013963\n 683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n 4000100011112224\n 09\n 16\n Longbob Longsen\n 123\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n \n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n \n
+ XML
+ end
+
+ def post_scrubbed
+ <<~XML
+ \n\n \n 1013963\n [FILTERED]\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n [FILTERED]\n 09\n 16\n Longbob Longsen\n [FILTERED]\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n \n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n \n
+ XML
+ end
+
+ def error_response
+ <<~XML
+ 103TargetNamespace required
+ XML
+ end
+
+ def successful_purchase_response
+ <<~XML
+ 0Approved20151201104518UTC-05:00000APRegularTotals1962962.00FullBatchCurrentDefaultNMVisa2005831886000045SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_purchase_with_echeck_response
+ <<~XML
+ 0Success20151202090320UTC-05:000Transaction ProcessedRegularTotalsFullBatchCurrentDefault2005838412347520966b3df3e93051b5dc85c355a54e3012c2SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTPending10FalseDefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_purchase_with_payment_account_token_response
+ <<~XML
+ 0Approved20151202090144UTC-05:00000APRegularTotals11552995.00FullBatchCurrentDefaultNVisa2005838405000001c0d498aa3c2c07169d13a989a7af91af5bc4e6a0SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownC875D86C-5913-487D-822E-76B27E2C2A4ECreditCard147b0b90f74faac13afb618fdabee3a4e75bf03bNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_credit_response
+ <<~XML
+ 0Approved20211122174635UTC-06:00000APRegularTotals1102103.00FullBatchCurrentVisa4000101228162530000461SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownNotUsedNotUsedCreditCardNullNullOneTimeFutureFalseActivePersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefault
+ XML
+ end
+
+ def failed_purchase_with_echeck_response
+ <<~XML
+ 101CardNumber Required20151202090342UTC-05:00RegularTotals1FullBatchCurrentDefault8fe3b762a2a4344d938c32be31f36e354fb28ee3SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_purchase_with_payment_account_token_response
+ <<~XML
+ 103PAYMENT ACCOUNT NOT FOUND20151202090245UTC-05:00RegularTotals1FullBatchCurrentDefault564bd4943761a37bdbb3f201faa56faa091781b5SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownasdfCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_purchase_response
+ <<~XML
+ 20Declined20151201104817UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005831909SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_authorize_response
+ <<~XML
+ 0Approved20151201120220UTC-05:00000APRegularTotals1FullBatchCurrentDefaultNMVisa2005832533000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTAuthorized5False1.00DefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_authorize_response
+ <<~XML
+ 20Declined20151201120315UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005832537SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_capture_response
+ <<~XML
+ 0Success20151201120222UTC-05:00000APRegularTotals1972963.00FullBatchCurrentDefaultVisa2005832535000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_capture_response
+ <<~XML
+ 101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_refund_response
+ <<~XML
+ 0Approved20151201120437UTC-05:00000APRegularTotals1992963.00FullBatchCurrentDefaultVisa2005832540000004SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_refund_response
+ <<~XML
+ 101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_void_response
+ <<~XML
+ 0Success20151201120516UTC-05:00006REVERSEDRegularTotalsFullBatchCurrentDefaultVisa2005832551000005SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTSuccess8FalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def failed_void_response
+ <<~XML
+ 101TransactionAmount requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNullOneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML
+ end
+
+ def successful_verify_response
+ <<~XML
+ 0Success20200505094556UTC-05:00000APRegularTotalsFullBatchCurrentNVisa400010481381541SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTSuccess8FalseDefaultUnknownNotUsedNotUsedCreditCardNullNull456 My StreetK1C2N6OneTimeFutureFalseActivePersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefault
+ XML
+ end
+end
From 2034c366f95011f026ef4b7d143ee2acc8e1bbdc Mon Sep 17 00:00:00 2001
From: cristian
Date: Wed, 15 Nov 2023 11:33:28 -0500
Subject: [PATCH 230/390] Rapyd: update force_3d_secure GSF behavior (#4955)
Summary:
------------------------------
Add changes to be more strict on how the `force_3d_secure` is
considered true.
* [SER-968](https://spreedly.atlassian.net/browse/SER-968)
Remote Test:
------------------------------
Finished in 239.011074 seconds.
42 tests, 118 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
97.619% passed
*Note:* The reason for the failing test is aun outdated wallet reference.
Unit Tests:
------------------------------
Finished in 38.879517 seconds.
5670 tests, 78363 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
778 files inspected, no offenses detected
---
lib/active_merchant/billing/gateways/rapyd.rb | 2 +-
test/unit/gateways/rapyd_test.rb | 16 ++++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index f3936b7a33e..90ddfeaf991 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -207,7 +207,7 @@ def add_tokens(post, payment, options)
def add_3ds(post, payment, options)
if options[:execute_threed] == true
- post[:payment_method_options] = { '3d_required' => true } if options[:force_3d_secure].present?
+ post[:payment_method_options] = { '3d_required' => true } if options[:force_3d_secure].to_s == 'true'
elsif three_d_secure = options[:three_d_secure]
post[:payment_method_options] = {}
post[:payment_method_options]['3d_required'] = three_d_secure[:required]
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index efebb6976e0..7223973d85e 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -185,6 +185,22 @@ def test_successful_purchase_with_3ds_gateway_specific
end.respond_with(successful_purchase_response)
end
+ def test_does_not_send_3ds_version_if_not_required
+ false_values = [false, nil, 'false', '']
+ @options[:execute_threed] = true
+
+ false_values.each do |value|
+ @options[:force_3d_secure] = value
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_nil request['payment_method_options']
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_request).returns(failed_purchase_response)
From 8667dd2ee0432958474301ca8d4f94015388562b Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Wed, 15 Nov 2023 14:47:31 -0500
Subject: [PATCH 231/390] Shift4 V2: Add unstore function (#4953)
Description
-------------------------
[SER-847](https://spreedly.atlassian.net/browse/SER-847)
This commit add unstore function for shift4 v2
Unit test
-------------------------
Finished in 0.066295 seconds.
38 tests, 209 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
573.20 tests/s, 3152.58 assertions/s
Remote test
-------------------------
Finished in 95.92829 seconds.
37 tests, 133 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
0.39 tests/s, 1.39 assertions/s
Rubocop
-------------------------
778 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
.../billing/gateways/shift4_v2.rb | 4 +++
test/remote/gateways/remote_shift4_v2_test.rb | 19 ++++++++++++
test/unit/gateways/shift4_v2_test.rb | 30 +++++++++++++++++++
4 files changed, 54 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index f12bbbb7033..b95818b1497 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -56,6 +56,7 @@
* CyberSource: surface the reconciliationID2 field [yunnydang] #4934
* Worldpay: Update stored credentials logic [DustinHaefele] #4950
* Vantiv Express: New Xml gateway [DustinHaefele] #4956
+* Shift4 V2: Add unstore function [javierpedrozaing] #4953
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/shift4_v2.rb b/lib/active_merchant/billing/gateways/shift4_v2.rb
index f06208c2883..d71733c7378 100644
--- a/lib/active_merchant/billing/gateways/shift4_v2.rb
+++ b/lib/active_merchant/billing/gateways/shift4_v2.rb
@@ -11,6 +11,10 @@ def credit(money, payment, options = {})
commit('credits', post, options)
end
+ def unstore(reference, options = {})
+ commit("customers/#{options[:customer_id]}/cards/#{reference}", nil, options, :delete)
+ end
+
def create_post_for_auth_or_purchase(money, payment, options)
super.tap do |post|
add_stored_credentials(post, options)
diff --git a/test/remote/gateways/remote_shift4_v2_test.rb b/test/remote/gateways/remote_shift4_v2_test.rb
index 357fbfadde4..7dc07760915 100644
--- a/test/remote/gateways/remote_shift4_v2_test.rb
+++ b/test/remote/gateways/remote_shift4_v2_test.rb
@@ -85,4 +85,23 @@ def test_failed_authorize
assert_equal response.authorization, response.params['error']['chargeId']
assert_equal response.message, 'The card was declined.'
end
+
+ def test_successful_store_and_unstore
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert card_id = store.params['defaultCardId']
+ assert customer_id = store.params['cards'][0]['customerId']
+ unstore = @gateway.unstore(card_id, customer_id: customer_id)
+ assert_success unstore
+ assert_equal unstore.params['id'], card_id
+ end
+
+ def test_failed_unstore
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert customer_id = store.params['cards'][0]['customerId']
+ unstore = @gateway.unstore(nil, customer_id: customer_id)
+ assert_failure unstore
+ assert_equal unstore.params['error']['type'], 'invalid_request'
+ end
end
diff --git a/test/unit/gateways/shift4_v2_test.rb b/test/unit/gateways/shift4_v2_test.rb
index 7bc4747aff3..9c2fd77a82d 100644
--- a/test/unit/gateways/shift4_v2_test.rb
+++ b/test/unit/gateways/shift4_v2_test.rb
@@ -27,6 +27,28 @@ def test_amount_gets_upcased_if_needed
end.respond_with(successful_purchase_response)
end
+ def test_successful_store_and_unstore
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ @gateway.expects(:ssl_post).returns(successful_new_customer_response)
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ @gateway.expects(:ssl_request).returns(successful_unstore_response)
+ unstore = @gateway.unstore('card_YhkJQlyF6NEc9RexV5dlZqTl', customer_id: 'cust_KDDJGACwxCUYkUb3fI76ERB7')
+ assert_success unstore
+ end
+
+ def test_successful_unstore
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.unstore('card_YhkJQlyF6NEc9RexV5dlZqTl', customer_id: 'cust_KDDJGACwxCUYkUb3fI76ERB7')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/cards/, data)
+ end.respond_with(successful_unstore_response)
+ assert response.success?
+ assert_equal response.message, 'Transaction approved'
+ end
+
private
def pre_scrubbed
@@ -88,4 +110,12 @@ def post_scrubbed
Conn close
POST_SCRUBBED
end
+
+ def successful_unstore_response
+ <<-RESPONSE
+ {
+ "id" : "card_G9xcxTDcjErIijO19SEWskN6"
+ }
+ RESPONSE
+ end
end
From 5a1c4a3b55c5de0abf6dfba628d89d5534a0d74d Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Wed, 15 Nov 2023 15:04:19 -0500
Subject: [PATCH 232/390] Description (#4957)
-------------------------
Add 3DS global support to Commerce Hub gateway
Tickets for Spreedly reference
SER-922
Unit test
-------------------------
Finished in 23.891463 seconds.
5671 tests, 78366 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
237.37 tests/s, 3280.08 assertions/s
Rubocop
-------------------------
778 files inspected, no offenses detected
Co-authored-by: Luis Urrea
---
CHANGELOG | 1 +
.../billing/gateways/commerce_hub.rb | 18 ++++++++++++++++++
.../gateways/remote_commerce_hub_test.rb | 15 +++++++++++++++
3 files changed, 34 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index b95818b1497..b07c15a9d37 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -57,6 +57,7 @@
* Worldpay: Update stored credentials logic [DustinHaefele] #4950
* Vantiv Express: New Xml gateway [DustinHaefele] #4956
* Shift4 V2: Add unstore function [javierpedrozaing] #4953
+* CommerceHub: Add 3DS global support [sinourain] #4957
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index e4d9acca748..2bad49fe0db 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -109,6 +109,23 @@ def scrub(transcript)
private
+ def add_three_d_secure(post, payment, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post[:additionalData3DS] = {
+ dsTransactionId: three_d_secure[:ds_transaction_id],
+ authenticationStatus: three_d_secure[:authentication_response_status],
+ serviceProviderTransactionId: three_d_secure[:three_ds_server_trans_id],
+ acsTransactionId: three_d_secure[:acs_transaction_id],
+ mpiData: {
+ cavv: three_d_secure[:cavv],
+ eci: three_d_secure[:eci],
+ xid: three_d_secure[:xid]
+ }.compact,
+ versionData: { recommendedVersion: three_d_secure[:version] }
+ }.compact
+ end
+
def add_transaction_interaction(post, options)
post[:transactionInteraction] = {}
post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
@@ -187,6 +204,7 @@ def add_shipping_address(post, options)
end
def build_purchase_and_auth_request(post, money, payment, options)
+ add_three_d_secure(post, payment, options)
add_invoice(post, money, options)
add_payment(post, payment, options)
add_stored_credentials(post, options)
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index c49ad1ebdaa..ff627614bb5 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -40,6 +40,14 @@ def setup
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
@master_card = credit_card('5454545454545454', brand: 'master')
@options = {}
+ @three_d_secure = {
+ ds_transaction_id: '3543-b90d-d6dc1765c98',
+ authentication_response_status: 'A',
+ cavv: 'AAABCZIhcQAAAABZlyFxAAAAAAA',
+ eci: '05',
+ xid: '&x_MD5_Hash=abfaf1d1df004e3c27d5d2e05929b529&x_state=BC&x_reference_3=&x_auth_code=ET141870&x_fp_timestamp=1231877695',
+ version: '2.2.0'
+ }
end
def test_successful_purchase
@@ -48,6 +56,13 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_3ds_purchase
+ @options.merge!(three_d_secure: @three_d_secure)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
def test_successful_purchase_whit_physical_goods_indicator
@options[:physical_goods_indicator] = true
response = @gateway.purchase(@amount, @credit_card, @options)
From 3b9de1fe94911ee16c1980f43a0d9e924eef21ad Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Fri, 17 Nov 2023 10:50:56 -0500
Subject: [PATCH 233/390] SumUp Gateway: Fix refund method (#4924)
Description
-------------------------
Fix refund method to SumUp Gateway adapter.
This are the relevant links to review the implementation:
- [Refund a transaction](https://developer.sumup.com/docs/api/refund-transaction/)
Tickets for Spreedly reference
SER-836
Note: SumUp has shared with us an account to test with which you can refund a purchase
Unit test
-------------------------
Finished in 32.469516 seconds.
5638 tests, 78183 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote test
-------------------------
173.64 tests/s, 2407.89 assertions/s
Rubocop
-------------------------
773 files inspected, no offenses detected
Co-authored-by: Luis
---
CHANGELOG | 1 +
.../billing/gateways/sum_up.rb | 81 ++++++++++++-------
test/fixtures.yml | 4 +
test/remote/gateways/remote_sum_up_test.rb | 26 ++++++
test/unit/gateways/sum_up_test.rb | 19 +++--
5 files changed, 95 insertions(+), 36 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index b07c15a9d37..ac3d2a64664 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -58,6 +58,7 @@
* Vantiv Express: New Xml gateway [DustinHaefele] #4956
* Shift4 V2: Add unstore function [javierpedrozaing] #4953
* CommerceHub: Add 3DS global support [sinourain] #4957
+* SumUp Gateway: Fix refund method [sinourain] #4924
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/sum_up.rb b/lib/active_merchant/billing/gateways/sum_up.rb
index c2824c9f39f..ea3e4e7a4b0 100644
--- a/lib/active_merchant/billing/gateways/sum_up.rb
+++ b/lib/active_merchant/billing/gateways/sum_up.rb
@@ -35,9 +35,8 @@ def void(authorization, options = {})
end
def refund(money, authorization, options = {})
- transaction_id = authorization.split('#')[-1]
- payment_currency = options[:currency] || currency(money)
- post = money ? { amount: localized_amount(money, payment_currency) } : {}
+ transaction_id = authorization.split('#').last
+ post = money ? { amount: amount(money) } : {}
add_merchant_data(post, options)
commit('me/refund/' + transaction_id, post)
@@ -106,10 +105,9 @@ def add_address(post, options)
end
def add_invoice(post, money, options)
- payment_currency = options[:currency] || currency(money)
post[:checkout_reference] = options[:order_id]
- post[:amount] = localized_amount(money, payment_currency)
- post[:currency] = payment_currency
+ post[:amount] = amount(money)
+ post[:currency] = options[:currency] || currency(money)
post[:description] = options[:description]
end
@@ -127,29 +125,31 @@ def add_payment(post, payment, options)
def commit(action, post, method = :post)
response = api_request(action, post.compact, method)
+ succeeded = success_from(response)
Response.new(
- success_from(response),
- message_from(response),
- response,
+ succeeded,
+ message_from(succeeded, response),
+ action.include?('refund') ? { response_code: response.to_s } : response,
authorization: authorization_from(response),
test: test?,
- error_code: error_code_from(response)
+ error_code: error_code_from(succeeded, response)
)
end
def api_request(action, post, method)
- begin
- raw_response = ssl_request(method, live_url + action, post.to_json, auth_headers)
- rescue ResponseError => e
- raw_response = e.response.body
- end
-
+ raw_response =
+ begin
+ ssl_request(method, live_url + action, post.to_json, auth_headers)
+ rescue ResponseError => e
+ e.response.body
+ end
response = parse(raw_response)
- # Multiple invalid parameters
- response = format_multiple_errors(response) if raw_response.include?('error_code') && response.is_a?(Array)
+ response = response.is_a?(Hash) ? response.symbolize_keys : response
- return response.symbolize_keys
+ return format_errors(response) if raw_response.include?('error_code') && response.is_a?(Array)
+
+ response
end
def parse(body)
@@ -157,6 +157,8 @@ def parse(body)
end
def success_from(response)
+ return true if response == 204
+
return false unless %w(PENDING EXPIRED PAID).include?(response[:status])
response[:transactions].each do |transaction|
@@ -166,13 +168,19 @@ def success_from(response)
true
end
- def message_from(response)
- return response[:status] if success_from(response)
+ def message_from(succeeded, response)
+ if succeeded
+ return 'Succeeded' if response.is_a?(Integer)
+
+ return response[:status]
+ end
response[:message] || response[:error_message]
end
def authorization_from(response)
+ return nil if response.is_a?(Integer)
+
return response[:id] unless response[:transaction_id]
[response[:id], response[:transaction_id]].join('#')
@@ -185,21 +193,36 @@ def auth_headers
}
end
- def error_code_from(response)
- response[:error_code] unless success_from(response)
+ def error_code_from(succeeded, response)
+ response[:error_code] unless succeeded
end
- def format_multiple_errors(responses)
- errors = responses.map do |response|
- { error_code: response['error_code'], param: response['param'] }
- end
-
+ def format_error(error, key)
{
+ :error_code => error['error_code'],
+ key => error['param']
+ }
+ end
+
+ def format_errors(errors)
+ return format_error(errors.first, :message) if errors.size == 1
+
+ return {
error_code: STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters],
message: 'Validation error',
- errors: errors
+ errors: errors.map { |error| format_error(error, :param) }
}
end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code (204) when the body is nil
+ when 200...300
+ response.body || response.code
+ else
+ raise ResponseError.new(response)
+ end
+ end
end
end
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 7cecdaafde8..69d60952694 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -1343,6 +1343,10 @@ sum_up:
access_token: SOMECREDENTIAL
pay_to_email: SOMECREDENTIAL
+sum_up_account_for_successful_purchases:
+ access_token: SOMECREDENTIAL
+ pay_to_email: SOMECREDENTIAL
+
# Working credentials, no need to replace
swipe_checkout:
login: 2077103073D8B5
diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb
index e9bc8d9582c..98468e7b80b 100644
--- a/test/remote/gateways/remote_sum_up_test.rb
+++ b/test/remote/gateways/remote_sum_up_test.rb
@@ -123,6 +123,32 @@ def test_failed_void_invalid_checkout_id
assert_equal 'Resource not found', response.message
end
+ # In Sum Up the account can only return checkout/purchase in pending or success status,
+ # to obtain a successful refund we will need an account that returns the checkout/purchase in successful status
+ #
+ # For this example configure in the fixtures => :sum_up_account_for_successful_purchases
+ def test_successful_refund
+ gateway = SumUpGateway.new(fixtures(:sum_up_account_for_successful_purchases))
+ purchase = gateway.purchase(@amount, @credit_card, @options)
+ transaction_id = purchase.params['transaction_id']
+ assert_not_nil transaction_id
+
+ response = gateway.refund(@amount, transaction_id, {})
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_partial_refund
+ gateway = SumUpGateway.new(fixtures(:sum_up_account_for_successful_purchases))
+ purchase = gateway.purchase(@amount * 10, @credit_card, @options)
+ transaction_id = purchase.params['transaction_id']
+ assert_not_nil transaction_id
+
+ response = gateway.refund(@amount, transaction_id, {})
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_failed_refund_for_pending_checkout
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
diff --git a/test/unit/gateways/sum_up_test.rb b/test/unit/gateways/sum_up_test.rb
index 5fc1995f19c..b19510fbab5 100644
--- a/test/unit/gateways/sum_up_test.rb
+++ b/test/unit/gateways/sum_up_test.rb
@@ -73,7 +73,7 @@ def test_success_from
def test_message_from
response = @gateway.send(:parse, successful_complete_checkout_response)
- message_from = @gateway.send(:message_from, response.symbolize_keys)
+ message_from = @gateway.send(:message_from, true, response.symbolize_keys)
assert_equal 'PENDING', message_from
end
@@ -83,15 +83,15 @@ def test_authorization_from
assert_equal '8d8336a1-32e2-4f96-820a-5c9ee47e76fc', authorization_from
end
- def test_format_multiple_errors
+ def test_format_errors
responses = @gateway.send(:parse, failed_complete_checkout_array_response)
- error_code = @gateway.send(:format_multiple_errors, responses)
- assert_equal format_multiple_errors_response, error_code
+ error_code = @gateway.send(:format_errors, responses)
+ assert_equal format_errors_response, error_code
end
def test_error_code_from
response = @gateway.send(:parse, failed_complete_checkout_response)
- error_code_from = @gateway.send(:error_code_from, response.symbolize_keys)
+ error_code_from = @gateway.send(:error_code_from, false, response.symbolize_keys)
assert_equal 'CHECKOUT_SESSION_IS_EXPIRED', error_code_from
end
@@ -422,6 +422,11 @@ def failed_complete_checkout_array_response
"message": "Validation error",
"param": "card",
"error_code": "The card is expired"
+ },
+ {
+ "message": "Validation error",
+ "param": "card",
+ "error_code": "The value located under the \'$.card.number\' path is not a valid card number"
}
]
RESPONSE
@@ -484,11 +489,11 @@ def failed_refund_response
RESPONSE
end
- def format_multiple_errors_response
+ def format_errors_response
{
error_code: 'MULTIPLE_INVALID_PARAMETERS',
message: 'Validation error',
- errors: [{ error_code: 'The card is expired', param: 'card' }]
+ errors: [{ error_code: 'The card is expired', param: 'card' }, { error_code: "The value located under the '$.card.number' path is not a valid card number", param: 'card' }]
}
end
end
From a7b2681e27a6f8355f41c7905df038e05a94f80a Mon Sep 17 00:00:00 2001
From: aenand
Date: Thu, 26 Oct 2023 13:57:42 -0400
Subject: [PATCH 234/390] Braintree: Add new stored credential method
ECS-3194
Braintree has informed us we are not handling stored credentials appropriately
in certain cases. This commit adds a new method to handle this new behavior in a
controlled manner by only doing so if a flag (new_stored_credential) indicates we should.
The changes are that
* If it's the initial_transaction & recurring it is to be marked
as `recurring_first`.
* Support for the `installment` reason type
* Map unscheduled to '' transaction_source
Test Summary
Remote:
111 tests, 585 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/braintree_blue.rb | 44 ++++++++-
.../gateways/remote_braintree_blue_test.rb | 37 ++++++++
test/unit/gateways/braintree_blue_test.rb | 93 +++++++++++++++++++
4 files changed, 170 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index ac3d2a64664..a3a2d17ba61 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -59,6 +59,7 @@
* Shift4 V2: Add unstore function [javierpedrozaing] #4953
* CommerceHub: Add 3DS global support [sinourain] #4957
* SumUp Gateway: Fix refund method [sinourain] #4924
+* Braintree: Add v2 stored credential option [aenand] #4937
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 91a27f00ec1..630862c1e6a 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -827,15 +827,39 @@ def xid_or_ds_trans_id(three_d_secure_opts)
end
def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
+ # Braintree has informed us that the stored_credential mapping may be incorrect
+ # In order to prevent possible breaking changes we will only apply the new logic if
+ # specifically requested. This will be the default behavior in a future release.
return unless (stored_credential = options[:stored_credential])
- parameters[:external_vault] = {}
- if stored_credential[:initial_transaction]
- parameters[:external_vault][:status] = 'will_vault'
+ add_external_vault(parameters, stored_credential)
+
+ if options[:stored_credentials_v2]
+ stored_credentials_v2(parameters, stored_credential)
else
- parameters[:external_vault][:status] = 'vaulted'
- parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id]
+ stored_credentials_v1(parameters, stored_credential)
+ end
+ end
+
+ def stored_credentials_v2(parameters, stored_credential)
+ # Differences between v1 and v2 are
+ # initial_transaction + recurring/installment should be labeled {{reason_type}}_first
+ # unscheduled in AM should map to '' at BT because unscheduled here means not on a fixed timeline or fixed amount
+ case stored_credential[:reason_type]
+ when 'recurring', 'installment'
+ if stored_credential[:initial_transaction]
+ parameters[:transaction_source] = "#{stored_credential[:reason_type]}_first"
+ else
+ parameters[:transaction_source] = stored_credential[:reason_type]
+ end
+ when 'recurring_first', 'moto'
+ parameters[:transaction_source] = stored_credential[:reason_type]
+ else
+ parameters[:transaction_source] = ''
end
+ end
+
+ def stored_credentials_v1(parameters, stored_credential)
if stored_credential[:initiator] == 'merchant'
if stored_credential[:reason_type] == 'installment'
parameters[:transaction_source] = 'recurring'
@@ -849,6 +873,16 @@ def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
end
end
+ def add_external_vault(parameters, stored_credential)
+ parameters[:external_vault] = {}
+ if stored_credential[:initial_transaction]
+ parameters[:external_vault][:status] = 'will_vault'
+ else
+ parameters[:external_vault][:status] = 'vaulted'
+ parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id]
+ end
+ end
+
def add_payment_method(parameters, credit_card_or_vault_id, options)
if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer)
add_third_party_token(parameters, credit_card_or_vault_id, options)
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 695315f7308..94022a75ca7 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -1014,6 +1014,43 @@ def test_verify_credentials
assert !gateway.verify_credentials
end
+ def test_successful_recurring_first_stored_credential_v2
+ creds_options = stored_credential_options(:cardholder, :recurring, :initial)
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_follow_on_recurring_first_cit_stored_credential_v2
+ creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_follow_on_recurring_first_mit_stored_credential_v2
+ creds_options = stored_credential_options(:merchant, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_one_time_mit_stored_credential_v2
+ creds_options = stored_credential_options(:merchant, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ end
+
def test_successful_merchant_purchase_initial
creds_options = stored_credential_options(:merchant, :recurring, :initial)
response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index 14662a8e1bc..524a71176c5 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -1351,6 +1351,21 @@ def test_stored_credential_recurring_first_cit_initial
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: { initiator: 'merchant', reason_type: 'recurring_first', initial_transaction: true } })
end
+ def test_stored_credential_v2_recurring_first_cit_initial
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'recurring_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: { initiator: 'merchant', reason_type: 'recurring_first', initial_transaction: true } })
+ end
+
def test_stored_credential_moto_cit_initial
Braintree::TransactionGateway.any_instance.expects(:sale).with(
standard_purchase_params.merge(
@@ -1366,6 +1381,84 @@ def test_stored_credential_moto_cit_initial
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: { initiator: 'merchant', reason_type: 'moto', initial_transaction: true } })
end
+ def test_stored_credential_v2_recurring_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'recurring_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:cardholder, :recurring, :initial) })
+ end
+
+ def test_stored_credential_v2_follow_on_recurring_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'vaulted',
+ previous_network_transaction_id: '123ABC'
+ },
+ transaction_source: 'recurring'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, :recurring, id: '123ABC') })
+ end
+
+ def test_stored_credential_v2_installment_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'installment_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:cardholder, :installment, :initial) })
+ end
+
+ def test_stored_credential_v2_follow_on_installment_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'vaulted',
+ previous_network_transaction_id: '123ABC'
+ },
+ transaction_source: 'installment'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, :installment, id: '123ABC') })
+ end
+
+ def test_stored_credential_v2_merchant_one_time
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'vaulted',
+ previous_network_transaction_id: '123ABC'
+ },
+ transaction_source: ''
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, id: '123ABC') })
+ end
+
def test_raises_exeption_when_adding_bank_account_to_customer_without_billing_address
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
From 22c1995e7e04360731acc3693341aa529e89f1f1 Mon Sep 17 00:00:00 2001
From: David Perry
Date: Tue, 21 Nov 2023 11:38:25 -0500
Subject: [PATCH 235/390] Cybersource REST: Remove request-target parens
(#4960)
Cybersource has updated their API to allow the Signature's
request-target header to no longer be enclosed in parentheses, ahead of
instating this as a requirement on January 22 2024:
https://community.developer.cybersource.com/t5/cybersource-Developer-Blog/REST-API-Http-Signature-Authentication-Critical-Security-Update/ba-p/87319
Remote:
47 tests, 157 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
47 tests, 157 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/cyber_source_rest.rb | 4 ++--
test/unit/gateways/cyber_source_rest_test.rb | 4 ++--
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index a3a2d17ba61..261b9d22558 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -60,6 +60,7 @@
* CommerceHub: Add 3DS global support [sinourain] #4957
* SumUp Gateway: Fix refund method [sinourain] #4924
* Braintree: Add v2 stored credential option [aenand] #4937
+* Cybersource REST: Remove request-target parens [curiousepic] #4960
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
index 28c4d9d6f12..2c6e1b28d24 100644
--- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -378,7 +378,7 @@ def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Tim
string_to_sign = {
host: host,
date: gmtdatetime,
- "(request-target)": "#{http_method} /pts/v2/#{resource}",
+ "request-target": "#{http_method} /pts/v2/#{resource}",
digest: digest,
"v-c-merchant-id": @options[:merchant_id]
}.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8)
@@ -386,7 +386,7 @@ def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Tim
{
keyid: @options[:public_key],
algorithm: 'HmacSHA256',
- headers: "host date (request-target)#{digest.present? ? ' digest' : ''} v-c-merchant-id",
+ headers: "host date request-target#{digest.present? ? ' digest' : ''} v-c-merchant-id",
signature: sign_payload(string_to_sign)
}.map { |k, v| %{#{k}="#{v}"} }.join(', ')
end
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
index f6ba1b40eba..0713e563652 100644
--- a/test/unit/gateways/cyber_source_rest_test.rb
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -98,7 +98,7 @@ def test_should_create_an_http_signature_for_a_post
assert_equal 'def345', parsed['keyid']
assert_equal 'HmacSHA256', parsed['algorithm']
- assert_equal 'host date (request-target) digest v-c-merchant-id', parsed['headers']
+ assert_equal 'host date request-target digest v-c-merchant-id', parsed['headers']
assert_equal %w[algorithm headers keyid signature], signature.split(', ').map { |v| v.split('=').first }.sort
end
@@ -106,7 +106,7 @@ def test_should_create_an_http_signature_for_a_get
signature = @gateway.send :get_http_signature, @resource, nil, 'get', @gmt_time
parsed = parse_signature(signature)
- assert_equal 'host date (request-target) v-c-merchant-id', parsed['headers']
+ assert_equal 'host date request-target v-c-merchant-id', parsed['headers']
end
def test_scrub
From fd6ec7b6f054fe923bf80b7691671102ad4a669a Mon Sep 17 00:00:00 2001
From: cristian
Date: Tue, 21 Nov 2023 13:51:48 -0500
Subject: [PATCH 236/390] Ogone: Fix signature calulcation for blank fields
(#4963)
Summary:
------------------------------
Add changes to deal with ogone signature when
there are empty fields on the passed params
[SER-965](https://spreedly.atlassian.net/browse/SER-965)
Remote Test:
------------------------------
Finished in 135.715931 seconds.
33 tests, 138 assertions, 1 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
96.9697% passed
Unit Tests:
------------------------------
Finished in 38.938916 seconds.
5733 tests, 78693 assertions, 0 failures, 0 errors,
0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
784 files inspected, no offenses detected
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/ogone.rb | 2 +-
test/unit/gateways/ogone_test.rb | 21 +++++++++++++++++++
3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG b/CHANGELOG
index 261b9d22558..4e347864070 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -61,6 +61,7 @@
* SumUp Gateway: Fix refund method [sinourain] #4924
* Braintree: Add v2 stored credential option [aenand] #4937
* Cybersource REST: Remove request-target parens [curiousepic] #4960
+* Ogone: Fix signature calulcation for blank fields [Heavyblade] #4963
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb
index 898c2ca8cd9..03b969d8df8 100644
--- a/lib/active_merchant/billing/gateways/ogone.rb
+++ b/lib/active_merchant/billing/gateways/ogone.rb
@@ -460,7 +460,7 @@ def calculate_signature(signed_parameters, algorithm, secret)
raise "Unknown signature algorithm #{algorithm}"
end
- filtered_params = signed_parameters.compact
+ filtered_params = signed_parameters.reject { |_k, v| v.nil? || v == '' }
sha_encryptor.hexdigest(
filtered_params.sort_by { |k, _v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('')
).upcase
diff --git a/test/unit/gateways/ogone_test.rb b/test/unit/gateways/ogone_test.rb
index 906ea36c184..8adb208a32a 100644
--- a/test/unit/gateways/ogone_test.rb
+++ b/test/unit/gateways/ogone_test.rb
@@ -453,6 +453,27 @@ def test_transcript_scrubbing
assert_equal @gateway.scrub(pre_scrub), post_scrub
end
+ def test_signatire_calculation_with_with_space
+ payload = {
+ orderID: 'abc123',
+ currency: 'EUR',
+ amount: '100',
+ PM: 'CreditCard',
+ ACCEPTANCE: 'test123',
+ STATUS: '9',
+ CARDNO: 'XXXXXXXXXXXX3310',
+ ED: '1029',
+ DCC_INDICATOR: '0',
+ DCC_EXCHRATE: ''
+ }
+
+ signature_with = @gateway.send(:calculate_signature, payload, 'sha512', 'ABC123')
+ payload.delete(:DCC_EXCHRATE)
+ signature_without = @gateway.send(:calculate_signature, payload, 'sha512', 'ABC123')
+
+ assert_equal signature_without, signature_with
+ end
+
private
def string_to_digest
From c0dd8533158178f133678c85a09b66ccb13693b0 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Thu, 16 Nov 2023 15:32:54 -0800
Subject: [PATCH 237/390] VisaNet Peru: Pass the purchaseNumber in response
---
CHANGELOG | 3 ++-
lib/active_merchant/billing/gateways/visanet_peru.rb | 2 +-
test/unit/gateways/visanet_peru_test.rb | 3 ++-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 4e347864070..f36bc54d967 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -53,7 +53,7 @@
* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
* Redsys Rest: Add support for new gateway type Redsys Rest [aenand] #4951
-* CyberSource: surface the reconciliationID2 field [yunnydang] #4934
+* CyberSource: Surface the reconciliationID2 field [yunnydang] #4934
* Worldpay: Update stored credentials logic [DustinHaefele] #4950
* Vantiv Express: New Xml gateway [DustinHaefele] #4956
* Shift4 V2: Add unstore function [javierpedrozaing] #4953
@@ -62,6 +62,7 @@
* Braintree: Add v2 stored credential option [aenand] #4937
* Cybersource REST: Remove request-target parens [curiousepic] #4960
* Ogone: Fix signature calulcation for blank fields [Heavyblade] #4963
+* VisaNet Peru: Add purchaseNumber to response object [yunnydang] #4961
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb
index 6bd87e406c1..5c98fadfb2f 100644
--- a/lib/active_merchant/billing/gateways/visanet_peru.rb
+++ b/lib/active_merchant/billing/gateways/visanet_peru.rb
@@ -148,7 +148,7 @@ def generate_purchase_number_stamp
def commit(action, params, options = {})
raw_response = ssl_request(method(action), url(action, params, options), params.to_json, headers)
- response = parse(raw_response)
+ response = parse(raw_response).merge('purchaseNumber' => params[:purchaseNumber])
rescue ResponseError => e
raw_response = e.response.body
response_error(raw_response, options, action)
diff --git a/test/unit/gateways/visanet_peru_test.rb b/test/unit/gateways/visanet_peru_test.rb
index 896f2bbb5a9..fdbfc995b4a 100644
--- a/test/unit/gateways/visanet_peru_test.rb
+++ b/test/unit/gateways/visanet_peru_test.rb
@@ -21,10 +21,11 @@ def setup
def test_successful_purchase
@gateway.expects(:ssl_request).with(:post, any_parameters).returns(successful_authorize_response)
@gateway.expects(:ssl_request).with(:put, any_parameters).returns(successful_capture_response)
-
response = @gateway.purchase(@amount, @credit_card, @options)
+
assert_success response
assert_equal 'OK', response.message
+ assert_not_nil response.params['purchaseNumber']
assert_match %r([0-9]{9}|$), response.authorization
assert_equal 'de9dc65c094fb4f1defddc562731af81', response.params['externalTransactionId']
From 7108e98f7e001a38bcf4efc4a72181dfa92083c0 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Tue, 7 Nov 2023 13:32:51 -0600
Subject: [PATCH 238/390] SafeCharge: Support tokens
Allow processing payments sith tokens. A token is returned by SafeCharge
in the previous response to represent the customers' credit card.
Remote:
34 tests, 96 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
30 tests, 160 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/safe_charge.rb | 50 +++++++++++++------
.../gateways/remote_safe_charge_test.rb | 10 ++++
test/unit/gateways/safe_charge_test.rb | 19 +++++++
4 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f36bc54d967..f7105b77977 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -63,6 +63,7 @@
* Cybersource REST: Remove request-target parens [curiousepic] #4960
* Ogone: Fix signature calulcation for blank fields [Heavyblade] #4963
* VisaNet Peru: Add purchaseNumber to response object [yunnydang] #4961
+* SafeCharge: Support tokens [almalee24] #4948
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index a4be0c6fcd1..3f522c0a1c0 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -149,26 +149,46 @@ def add_transaction_data(trans_type, post, money, options)
end
def add_payment(post, payment, options = {})
- post[:sg_ExpMonth] = format(payment.month, :two_digits)
- post[:sg_ExpYear] = format(payment.year, :two_digits)
- post[:sg_CardNumber] = payment.number
-
- if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
- post[:sg_CAVV] = payment.payment_cryptogram
- post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
- post[:sg_IsExternalMPI] = 1
- post[:sg_ExternalTokenProvider] = 5
- else
- post[:sg_CVV2] = payment.verification_value
- post[:sg_NameOnCard] = payment.name
- post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ case payment
+ when String
+ add_token(post, payment)
+ when CreditCard
+ post[:sg_ExpMonth] = format(payment.month, :two_digits)
+ post[:sg_ExpYear] = format(payment.year, :two_digits)
+ post[:sg_CardNumber] = payment.number
+
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
+ add_network_token(post, payment, options)
+ else
+ add_credit_card(post, payment, options)
+ end
end
end
+ def add_token(post, payment)
+ _, transaction_id, token = payment.split('|')
+
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ end
+
+ def add_credit_card(post, payment, options)
+ post[:sg_CVV2] = payment.verification_value
+ post[:sg_NameOnCard] = payment.name
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ end
+
+ def add_network_token(post, payment, options)
+ post[:sg_CAVV] = payment.payment_cryptogram
+ post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
+ post[:sg_IsExternalMPI] = 1
+ post[:sg_ExternalTokenProvider] = 5
+ end
+
def add_customer_details(post, payment, options)
if address = options[:billing_address] || options[:address]
- post[:sg_FirstName] = payment.first_name
- post[:sg_LastName] = payment.last_name
+ post[:sg_FirstName] = payment.first_name if payment.respond_to?(:first_name)
+ post[:sg_LastName] = payment.last_name if payment.respond_to?(:last_name)
post[:sg_Address] = address[:address1] if address[:address1]
post[:sg_City] = address[:city] if address[:city]
post[:sg_State] = address[:state] if address[:state]
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 8e8fe4dd945..5c9d81fc6b6 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -63,6 +63,16 @@ def test_successful_purchase
assert_equal 'Success', response.message
end
+ def test_successful_purchase_with_token
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ subsequent_response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success subsequent_response
+ assert_equal 'Success', subsequent_response.message
+ end
+
def test_successful_purchase_with_non_fractional_currency
options = @options.merge(currency: 'CLP')
response = @gateway.purchase(127999, @credit_card, options)
diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb
index 23a579e569f..06ba3574287 100644
--- a/test/unit/gateways/safe_charge_test.rb
+++ b/test/unit/gateways/safe_charge_test.rb
@@ -108,6 +108,25 @@ def test_successful_purchase_with_falsey_stored_credential_mode
assert purchase.test?
end
+ def test_successful_purchase_with_token
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ _, transaction_id = response.authorization.split('|')
+ subsequent_response = stub_comms do
+ @gateway.purchase(@amount, response.authorization, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/sg_CCToken/, data)
+ assert_match(/sg_TransactionID=#{transaction_id}/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success subsequent_response
+ assert_equal 'Success', subsequent_response.message
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_post).returns(failed_purchase_response)
From b5810bdff1b0c64802128341674dd4f703e7ee07 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Fri, 3 Nov 2023 10:30:38 -0500
Subject: [PATCH 239/390] Redsys: Update to $0 verify
Remote:
22 tests, 54 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
86.3636% passed
Unit:
34 tests, 117 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Redsys sha256
Remote:
32 tests, 109 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Unit:
48 tests, 192 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/redsys.rb | 9 +-----
.../gateways/remote_redsys_sha256_test.rb | 15 ++++------
test/remote/gateways/remote_redsys_test.rb | 11 ++------
test/unit/gateways/redsys_sha256_test.rb | 11 ++------
test/unit/gateways/redsys_test.rb | 28 +++++++++++++------
6 files changed, 31 insertions(+), 44 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f7105b77977..c6700067b8b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -64,6 +64,7 @@
* Ogone: Fix signature calulcation for blank fields [Heavyblade] #4963
* VisaNet Peru: Add purchaseNumber to response object [yunnydang] #4961
* SafeCharge: Support tokens [almalee24] #4948
+* Redsys: Update to $0 verify [almalee24] #4944
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb
index 260c4d7d83f..43837744a2d 100644
--- a/lib/active_merchant/billing/gateways/redsys.rb
+++ b/lib/active_merchant/billing/gateways/redsys.rb
@@ -266,14 +266,7 @@ def refund(money, authorization, options = {})
end
def verify(creditcard, options = {})
- if options[:sca_exemption_behavior_override] == 'endpoint_and_ntid'
- purchase(0, creditcard, options)
- else
- MultiResponse.run(:use_first_response) do |r|
- r.process { authorize(100, creditcard, options) }
- r.process(:ignore_result) { void(r.authorization, options) }
- end
- end
+ purchase(0, creditcard, options)
end
def supports_scrubbing
diff --git a/test/remote/gateways/remote_redsys_sha256_test.rb b/test/remote/gateways/remote_redsys_sha256_test.rb
index 334e4cc21ca..bd36ae2bb72 100644
--- a/test/remote/gateways/remote_redsys_sha256_test.rb
+++ b/test/remote/gateways/remote_redsys_sha256_test.rb
@@ -105,7 +105,7 @@ def test_successful_authorize_3ds
response = @gateway.authorize(@amount, @credit_card, options)
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
assert response.authorization
end
@@ -132,7 +132,7 @@ def test_successful_3ds_authorize_with_exemption
response = @gateway.authorize(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
end
@@ -141,7 +141,7 @@ def test_successful_3ds_purchase_with_exemption
response = @gateway.purchase(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
end
@@ -364,12 +364,9 @@ def test_failed_void
authorize = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorize
- void = @gateway.void(authorize.authorization)
- assert_success void
-
- another_void = @gateway.void(authorize.authorization)
+ another_void = @gateway.void(authorize.authorization << '123')
assert_failure another_void
- assert_equal 'SIS0222 ERROR', another_void.message
+ assert_equal 'SIS0007 ERROR', another_void.message
end
def test_successful_verify
@@ -377,8 +374,6 @@ def test_successful_verify
assert_success response
assert_equal 'Transaction Approved', response.message
- assert_success response.responses.last, 'The void should succeed'
- assert_equal 'Cancellation Accepted', response.responses.last.message
end
def test_unsuccessful_verify
diff --git a/test/remote/gateways/remote_redsys_test.rb b/test/remote/gateways/remote_redsys_test.rb
index b8c790d30f9..eef1dc8a108 100644
--- a/test/remote/gateways/remote_redsys_test.rb
+++ b/test/remote/gateways/remote_redsys_test.rb
@@ -175,12 +175,9 @@ def test_failed_void
authorize = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorize
- void = @gateway.void(authorize.authorization)
- assert_success void
-
- another_void = @gateway.void(authorize.authorization)
- assert_failure another_void
- assert_equal 'SIS0222 ERROR', another_void.message
+ void = @gateway.void(authorize.authorization << '123')
+ assert_failure void
+ assert_equal 'SIS0007 ERROR', void.message
end
def test_successful_verify
@@ -188,8 +185,6 @@ def test_successful_verify
assert_success response
assert_equal 'Transaction Approved', response.message
- assert_success response.responses.last, 'The void should succeed'
- assert_equal 'Cancellation Accepted', response.responses.last.message
end
def test_unsuccessful_verify
diff --git a/test/unit/gateways/redsys_sha256_test.rb b/test/unit/gateways/redsys_sha256_test.rb
index 66dae34a2c0..7fdf18331ea 100644
--- a/test/unit/gateways/redsys_sha256_test.rb
+++ b/test/unit/gateways/redsys_sha256_test.rb
@@ -361,20 +361,13 @@ def test_override_currency
end
def test_successful_verify
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response)
- response = @gateway.verify(credit_card, order_id: '144743367273')
- assert_success response
- end
-
- def test_successful_verify_with_failed_void
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response)
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
response = @gateway.verify(credit_card, order_id: '144743367273')
assert_success response
- assert_equal 'Transaction Approved', response.message
end
def test_unsuccessful_verify
- @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
response = @gateway.verify(credit_card, order_id: '141278225678')
assert_failure response
assert_equal 'SIS0093 ERROR', response.message
diff --git a/test/unit/gateways/redsys_test.rb b/test/unit/gateways/redsys_test.rb
index 605d077e38a..e27e5a1bac5 100644
--- a/test/unit/gateways/redsys_test.rb
+++ b/test/unit/gateways/redsys_test.rb
@@ -316,23 +316,33 @@ def test_override_currency
end
def test_successful_verify
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response)
- response = @gateway.verify(credit_card, @options)
- assert_success response
- end
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes(CGI.escape('0')),
+ includes(CGI.escape('0'))
+ ),
+ anything
+ ).returns(successful_purchase_response)
- def test_successful_verify_with_failed_void
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response)
response = @gateway.verify(credit_card, @options)
+
assert_success response
- assert_equal 'Transaction Approved', response.message
end
def test_unsuccessful_verify
- @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ @gateway.expects(:ssl_post).with(
+ anything,
+ all_of(
+ includes(CGI.escape('0')),
+ includes(CGI.escape('0'))
+ ),
+ anything
+ ).returns(failed_purchase_response)
+
response = @gateway.verify(credit_card, @options)
+
assert_failure response
- assert_equal 'SIS0093 ERROR', response.message
end
def test_unknown_currency
From 482a4e917d56d27388ecf41520a26f0efd48bc3c Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Mon, 2 Oct 2023 13:13:34 -0500
Subject: [PATCH 240/390] Litle: Update stored credentials
Update how stored credential transactions are handle.
Unit:
58 tests, 257 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
57 tests, 251 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/litle.rb | 65 +++++++------------
test/remote/gateways/remote_litle_test.rb | 2 +
test/unit/gateways/litle_test.rb | 8 ++-
4 files changed, 32 insertions(+), 44 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index c6700067b8b..f131afc69f3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -65,6 +65,7 @@
* VisaNet Peru: Add purchaseNumber to response object [yunnydang] #4961
* SafeCharge: Support tokens [almalee24] #4948
* Redsys: Update to $0 verify [almalee24] #4944
+* Litle: Update stored credentials [almalee24] #4903
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb
index ac5e4c66f5b..10ab54ac45e 100644
--- a/lib/active_merchant/billing/gateways/litle.rb
+++ b/lib/active_merchant/billing/gateways/litle.rb
@@ -310,7 +310,15 @@ def add_authentication(doc)
def add_auth_purchase_params(doc, money, payment_method, options)
doc.orderId(truncate(options[:order_id], 24))
doc.amount(money)
- add_order_source(doc, payment_method, options)
+
+ if options.dig(:stored_credential, :initial_transaction) == false
+ # orderSource needs to be added at the top of doc and
+ # processingType near the end
+ source_for_subsequent_stored_credential_txns(doc, options)
+ else
+ add_order_source(doc, payment_method, options)
+ end
+
add_billing_address(doc, payment_method, options)
add_shipping_address(doc, payment_method, options)
add_payment_method(doc, payment_method, options)
@@ -409,16 +417,17 @@ def add_payment_method(doc, payment_method, options)
end
def add_stored_credential_params(doc, options = {})
- return unless options[:stored_credential]
+ return unless stored_credential = options[:stored_credential]
- if options[:stored_credential][:initial_transaction]
- add_stored_credential_params_initial(doc, options)
+ if stored_credential[:initial_transaction]
+ add_stored_credential_for_initial_txn(doc, options)
else
- add_stored_credential_params_used(doc, options)
+ doc.processingType("#{stored_credential[:initiator]}InitiatedCOF") if stored_credential[:reason_type] == 'unscheduled'
+ doc.originalNetworkTransactionId(stored_credential[:network_transaction_id]) if stored_credential[:initiator] == 'merchant'
end
end
- def add_stored_credential_params_initial(doc, options)
+ def add_stored_credential_for_initial_txn(doc, options)
case options[:stored_credential][:reason_type]
when 'unscheduled'
doc.processingType('initialCOF')
@@ -429,15 +438,15 @@ def add_stored_credential_params_initial(doc, options)
end
end
- def add_stored_credential_params_used(doc, options)
- if options[:stored_credential][:reason_type] == 'unscheduled'
- if options[:stored_credential][:initiator] == 'merchant'
- doc.processingType('merchantInitiatedCOF')
- else
- doc.processingType('cardholderInitiatedCOF')
- end
+ def source_for_subsequent_stored_credential_txns(doc, options)
+ case options[:stored_credential][:reason_type]
+ when 'unscheduled'
+ doc.orderSource('ecommerce')
+ when 'installment'
+ doc.orderSource('installment')
+ when 'recurring'
+ doc.orderSource('recurring')
end
- doc.originalNetworkTransactionId(options[:stored_credential][:network_transaction_id])
end
def add_billing_address(doc, payment_method, options)
@@ -479,8 +488,7 @@ def add_address(doc, address)
end
def add_order_source(doc, payment_method, options)
- order_source = order_source(options)
- if order_source
+ if order_source = options[:order_source]
doc.orderSource(order_source)
elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
doc.orderSource('applepay')
@@ -493,31 +501,6 @@ def add_order_source(doc, payment_method, options)
end
end
- def order_source(options = {})
- return options[:order_source] unless options[:stored_credential]
-
- order_source = nil
-
- case options[:stored_credential][:reason_type]
- when 'unscheduled'
- if options[:stored_credential][:initiator] == 'merchant'
- # For merchant-initiated, we should always set order source to
- # 'ecommerce'
- order_source = 'ecommerce'
- else
- # For cardholder-initiated, we rely on #add_order_source's
- # default logic to set orderSource appropriately
- order_source = options[:order_source]
- end
- when 'installment'
- order_source = 'installment'
- when 'recurring'
- order_source = 'recurring'
- end
-
- order_source
- end
-
def add_pos(doc, payment_method)
return unless payment_method.respond_to?(:track_data) && payment_method.track_data.present?
diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb
index 80241a8b361..e44da1776af 100644
--- a/test/remote/gateways/remote_litle_test.rb
+++ b/test/remote/gateways/remote_litle_test.rb
@@ -453,6 +453,7 @@ def test_authorize_and_capture_with_stored_credential_recurring
network_transaction_id: network_transaction_id
}
)
+
assert auth = @gateway.authorize(4999, credit_card, used_options)
assert_success auth
assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', auth.message
@@ -632,6 +633,7 @@ def test_purchase_with_stored_credential_cit_card_on_file_non_ecommerce
}
)
assert auth = @gateway.purchase(4000, credit_card, used_options)
+
assert_success auth
assert_equal 'Approved', auth.message
end
diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb
index 4c2314894aa..e7c8e554a7a 100644
--- a/test/unit/gateways/litle_test.rb
+++ b/test/unit/gateways/litle_test.rb
@@ -597,7 +597,7 @@ def test_stored_credential_cit_card_on_file_used
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(%r(cardholderInitiatedCOF), data)
- assert_match(%r(#{network_transaction_id}), data)
+ assert_not_match(%r(#{network_transaction_id}), data)
assert_match(%r(ecommerce), data)
end.respond_with(successful_authorize_stored_credentials)
@@ -621,8 +621,8 @@ def test_stored_credential_cit_cof_doesnt_override_order_source
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(%r(cardholderInitiatedCOF), data)
- assert_match(%r(#{network_transaction_id}), data)
- assert_match(%r(3dsAuthenticated), data)
+ assert_not_match(%r(#{network_transaction_id}), data)
+ assert_match(%r(ecommerce), data)
end.respond_with(successful_authorize_stored_credentials)
assert_success response
@@ -641,6 +641,7 @@ def test_stored_credential_mit_card_on_file_initial
response = stub_comms do
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
+ assert_match(%r(ecommerce), data)
assert_match(%r(initialCOF), data)
end.respond_with(successful_authorize_stored_credentials)
@@ -700,6 +701,7 @@ def test_stored_credential_installment_used
response = stub_comms do
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
+ assert_not_match(%r(), data)
assert_match(%r(#{network_transaction_id}), data)
assert_match(%r(installment), data)
end.respond_with(successful_authorize_stored_credentials)
From 2e6d068de54152868d128f73c2731ece79c3a1f0 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 2 Nov 2023 11:27:20 -0500
Subject: [PATCH 241/390] WorldPay: Accept GooglePay pan only
Update payment_details and add_network_tokenization_card to accept
GooglePay payment methods that are pan only.
Remote:
103 tests, 416 assertions, 7 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
93.2039% passed
Unit:
114 tests, 648 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/worldpay.rb | 19 ++++++++-------
test/remote/gateways/remote_worldpay_test.rb | 24 +++++++++++++++++++
test/unit/gateways/worldpay_test.rb | 8 +++++++
4 files changed, 43 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index f131afc69f3..66886e88741 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -66,6 +66,7 @@
* SafeCharge: Support tokens [almalee24] #4948
* Redsys: Update to $0 verify [almalee24] #4944
* Litle: Update stored credentials [almalee24] #4903
+* WorldPay: Accept GooglePay pan only [almalee24] #4943
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index e66fccc79f1..9d77455b005 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -62,7 +62,7 @@ def purchase(money, payment_method, options = {})
def authorize(money, payment_method, options = {})
requires!(options, :order_id)
- payment_details = payment_details(payment_method)
+ payment_details = payment_details(payment_method, options)
authorize_request(money, payment_method, payment_details.merge(options))
end
@@ -108,7 +108,7 @@ def refund(money, authorization, options = {})
# and other transactions should be performed on a normal eCom-flagged
# merchant ID.
def credit(money, payment_method, options = {})
- payment_details = payment_details(payment_method)
+ payment_details = payment_details(payment_method, options)
if options[:fast_fund_credit]
fast_fund_credit_request(money, payment_method, payment_details.merge(credit: true, **options))
else
@@ -572,7 +572,7 @@ def add_payment_method(xml, amount, payment_method, options)
when :pay_as_order
add_amount_for_pay_as_order(xml, amount, payment_method, options)
when :network_token
- add_network_tokenization_card(xml, payment_method)
+ add_network_tokenization_card(xml, payment_method, options)
else
add_card_or_token(xml, payment_method, options)
end
@@ -590,8 +590,9 @@ def add_amount_for_pay_as_order(xml, amount, payment_method, options)
end
end
- def add_network_tokenization_card(xml, payment_method)
- token_type = NETWORK_TOKEN_TYPE.fetch(payment_method.source, 'NETWORKTOKEN')
+ def add_network_tokenization_card(xml, payment_method, options)
+ source = payment_method.respond_to?(:source) ? payment_method.source : options[:wallet_type]
+ token_type = NETWORK_TOKEN_TYPE.fetch(source, 'NETWORKTOKEN')
xml.paymentDetails do
xml.tag! 'EMVCO_TOKEN-SSL', 'type' => token_type do
@@ -603,9 +604,9 @@ def add_network_tokenization_card(xml, payment_method)
)
end
name = card_holder_name(payment_method, options)
- eci = format(payment_method.eci, :two_digits)
+ eci = payment_method.respond_to?(:eci) ? format(payment_method.eci, :two_digits) : ''
xml.cardHolderName name if name.present?
- xml.cryptogram payment_method.payment_cryptogram
+ xml.cryptogram payment_method.payment_cryptogram unless options[:wallet_type] == :google_pay
xml.eciIndicator eci.empty? ? '07' : eci
end
end
@@ -975,12 +976,12 @@ def token_details_from_authorization(authorization)
token_details
end
- def payment_details(payment_method)
+ def payment_details(payment_method, options = {})
case payment_method
when String
token_type_and_details(payment_method)
else
- type = network_token?(payment_method) ? :network_token : :credit
+ type = network_token?(payment_method) || options[:wallet_type] == :google_pay ? :network_token : :credit
{ payment_type: type }
end
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index c59571cf98e..4d0632e8c64 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -197,6 +197,30 @@ def test_successful_authorize_with_card_holder_name_google_pay
assert_equal 'SUCCESS', response.message
end
+ def test_successful_authorize_with_google_pay_pan_only
+ response = @gateway.authorize(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_purchase_with_google_pay_pan_only
+ assert auth = @gateway.purchase(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+ assert auth.authorization
+ end
+
+ def test_successful_authorize_with_void_google_pay_pan_only
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success auth
+ assert_equal 'authorize', auth.params['action']
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(authorization_validated: true))
+ assert_success capture
+ assert void = @gateway.void(auth.authorization, @options.merge(authorization_validated: true))
+ assert_success void
+ end
+
def test_successful_authorize_without_card_holder_name_apple_pay
@apple_pay_network_token.first_name = ''
@apple_pay_network_token.last_name = ''
diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb
index d7dfe541321..f2dc5f177c5 100644
--- a/test/unit/gateways/worldpay_test.rb
+++ b/test/unit/gateways/worldpay_test.rb
@@ -1463,6 +1463,14 @@ def test_network_token_type_assignation_when_google_pay
end
end
+ def test_network_token_type_assignation_when_google_pay_pan_only
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match %r(), data
+ end
+ end
+
def test_order_id_crop_and_clean
@options[:order_id] = "abc1234 abc1234 'abc1234' \"abc1234\" | abc1234 abc1234 abc1234 abc1234 abc1234"
response = stub_comms do
From 242b22bdb883fe48065998917018d2f00325e22a Mon Sep 17 00:00:00 2001
From: aenand
Date: Wed, 22 Nov 2023 15:19:11 -0500
Subject: [PATCH 242/390] Braintree stored creds v2: update unschedule
Ensure that MIT unscheduled transactions get mapped to unscheduled
at Braintree
Remote:
111 tests, 585 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/braintree_blue.rb | 2 ++
test/unit/gateways/braintree_blue_test.rb | 22 +++++++++++++++----
3 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 66886e88741..fb3d3c104c7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -67,6 +67,7 @@
* Redsys: Update to $0 verify [almalee24] #4944
* Litle: Update stored credentials [almalee24] #4903
* WorldPay: Accept GooglePay pan only [almalee24] #4943
+* Braintree: Correct issue in v2 stored credentials [aenand] #4967
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 630862c1e6a..26cc0d402cb 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -854,6 +854,8 @@ def stored_credentials_v2(parameters, stored_credential)
end
when 'recurring_first', 'moto'
parameters[:transaction_source] = stored_credential[:reason_type]
+ when 'unscheduled'
+ parameters[:transaction_source] = stored_credential[:initiator] == 'merchant' ? stored_credential[:reason_type] : ''
else
parameters[:transaction_source] = ''
end
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index 524a71176c5..25e0c8f13fd 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -1443,20 +1443,34 @@ def test_stored_credential_v2_follow_on_installment_first
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, :installment, id: '123ABC') })
end
- def test_stored_credential_v2_merchant_one_time
+ def test_stored_credential_v2_unscheduled_cit_initial
Braintree::TransactionGateway.any_instance.expects(:sale).with(
standard_purchase_params.merge(
{
external_vault: {
- status: 'vaulted',
- previous_network_transaction_id: '123ABC'
+ status: 'will_vault'
},
transaction_source: ''
}
)
).returns(braintree_result)
- @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, id: '123ABC') })
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:cardholder, :unscheduled, :initial) })
+ end
+
+ def test_stored_credential_v2_unscheduled_mit_initial
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'unscheduled'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, :unscheduled, :initial) })
end
def test_raises_exeption_when_adding_bank_account_to_customer_without_billing_address
From bab2f499329217e9a375abdff75773c56808ec29 Mon Sep 17 00:00:00 2001
From: yunnydang
Date: Mon, 20 Nov 2023 17:31:59 -0800
Subject: [PATCH 243/390] Stripe PI: add the card brand field
---
CHANGELOG | 1 +
.../gateways/stripe_payment_intents.rb | 10 ++++
.../remote_stripe_payment_intents_test.rb | 48 +++++++++++++++++++
.../gateways/stripe_payment_intents_test.rb | 10 ++++
4 files changed, 69 insertions(+)
diff --git a/CHANGELOG b/CHANGELOG
index fb3d3c104c7..27f7b4205e4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -68,6 +68,7 @@
* Litle: Update stored credentials [almalee24] #4903
* WorldPay: Accept GooglePay pan only [almalee24] #4943
* Braintree: Correct issue in v2 stored credentials [aenand] #4967
+* Stripe Payment Intents: Add the card brand field [yunnydang] #4964
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index f423ff39aa5..0c091bcbece 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -44,6 +44,7 @@ def create_intent(money, payment_method, options = {})
add_fulfillment_date(post, options)
request_three_d_secure(post, options)
add_level_three(post, options)
+ add_card_brand(post, options)
post[:expand] = ['charges.data.balance_transaction']
CREATE_INTENT_ATTRIBUTES.each do |attribute|
@@ -140,6 +141,7 @@ def create_setup_intent(payment_method, options = {})
add_return_url(post, options)
add_fulfillment_date(post, options)
request_three_d_secure(post, options)
+ add_card_brand(post, options)
post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
post[:description] = options[:description] if options[:description]
@@ -323,6 +325,14 @@ def add_metadata(post, options = {})
post[:metadata][:event_type] = options[:event_type] if options[:event_type]
end
+ def add_card_brand(post, options)
+ return unless options[:card_brand]
+
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:network] = options[:card_brand] if options[:card_brand]
+ end
+
def add_level_three(post, options = {})
level_three = {}
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index c4b1267d382..29005e092bf 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -55,6 +55,13 @@ def setup
year: 2028
)
+ @visa_card_brand_choice = credit_card(
+ '4000002500001001',
+ verification_value: '737',
+ month: 10,
+ year: 2028
+ )
+
@google_pay = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
@@ -119,6 +126,20 @@ def test_successful_purchase
assert purchase.params.dig('charges', 'data')[0]['balance_transaction']
end
+ def test_successful_purchase_with_card_brand
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ card_brand: 'cartes_bancaires'
+ }
+ assert purchase = @gateway.purchase(@amount, @visa_card_brand_choice, options)
+ assert_equal 'succeeded', purchase.params['status']
+
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['balance_transaction']
+ assert_equal purchase.params['payment_method_options']['card']['network'], 'cartes_bancaires'
+ end
+
def test_successful_purchase_with_shipping_address
options = {
currency: 'GBP',
@@ -636,6 +657,33 @@ def test_create_setup_intent_with_setup_future_usage
end
end
+ def test_create_setup_intent_with_setup_future_usage_and_card_brand
+ response = @gateway.create_setup_intent(@visa_card_brand_choice, {
+ address: {
+ email: 'test@example.com',
+ name: 'John Doe',
+ line1: '1 Test Ln',
+ city: 'Durham',
+ tracking_number: '123456789'
+ },
+ currency: 'USD',
+ card_brand: 'cartes_bancaires',
+ confirm: true,
+ execute_threed: true,
+ return_url: 'https://example.com'
+ })
+
+ assert_equal 'succeeded', response.params['status']
+ assert_equal response.params['payment_method_options']['card']['network'], 'cartes_bancaires'
+ # since we cannot "click" the stripe hooks URL to confirm the authorization
+ # we will at least confirm we can retrieve the created setup_intent and it contains the structure we expect
+ setup_intent_id = response.params['id']
+ assert si_response = @gateway.retrieve_setup_intent(setup_intent_id)
+
+ assert_equal 'succeeded', si_response.params['status']
+ assert_not_empty si_response.params.dig('latest_attempt', 'payment_method_details', 'card')
+ end
+
def test_create_setup_intent_with_connected_account
[@three_ds_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
assert authorize_response = @gateway.create_setup_intent(card_to_use, {
diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb
index 610e7542d28..3027de3c04c 100644
--- a/test/unit/gateways/stripe_payment_intents_test.rb
+++ b/test/unit/gateways/stripe_payment_intents_test.rb
@@ -371,6 +371,16 @@ def test_successful_purchase_with_level3_data
end.respond_with(successful_create_intent_response)
end
+ def test_successful_purchase_with_card_brand
+ @options[:card_brand] = 'cartes_bancaires'
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @visa_token, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match('payment_method_options[card][network]=cartes_bancaires', data)
+ end.respond_with(successful_create_intent_response)
+ end
+
def test_succesful_purchase_with_stored_credentials_without_sending_ntid
[@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
network_transaction_id = '1098510912210968'
From a4ae6af1b7f507c8df83d22af4f4de5c274dbac4 Mon Sep 17 00:00:00 2001
From: Javier Pedroza
Date: Thu, 30 Nov 2023 15:06:56 -0500
Subject: [PATCH 244/390] Rapyd: Enable new auth mode payment_redirect (#4970)
Description
-------------------------
[SER-970](https://spreedly.atlassian.net/browse/SER-974)
This commit enable a new mode of authentication for Rapyd.
this new mode only apply for create payments
Unit test
-------------------------
Finished in 0.189728 seconds.
41 tests, 188 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
216.10 tests/s, 990.89 assertions/s
Remote test
-------------------------
Finished in 208.640892 seconds.
47 tests, 123 assertions, 6 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
87.234% passed
0.23 tests/s, 0.59 assertions/s
Rubocop
-------------------------
784 files inspected, no offenses detected
Co-authored-by: Javier Pedroza
---
CHANGELOG | 1 +
lib/active_merchant/billing/gateways/rapyd.rb | 16 +++++++-
test/remote/gateways/remote_rapyd_test.rb | 41 ++++++++++++++++++-
test/unit/gateways/rapyd_test.rb | 22 ++++++++++
4 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 27f7b4205e4..3318fa8101c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -69,6 +69,7 @@
* WorldPay: Accept GooglePay pan only [almalee24] #4943
* Braintree: Correct issue in v2 stored credentials [aenand] #4967
* Stripe Payment Intents: Add the card brand field [yunnydang] #4964
+* Rapyd: Enable new auth mode payment_redirect [javierpedrozaing] #4970
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 90ddfeaf991..1f910dc1d15 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -1,9 +1,14 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class RapydGateway < Gateway
+ class_attribute :payment_redirect_test, :payment_redirect_live
+
self.test_url = 'https://sandboxapi.rapyd.net/v1/'
self.live_url = 'https://api.rapyd.net/v1/'
+ self.payment_redirect_test = 'https://sandboxpayment-redirect.rapyd.net/v1/'
+ self.payment_redirect_live = 'https://payment-redirect.rapyd.net/v1/'
+
self.supported_countries = %w(CA CL CO DO SV PE PT VI AU HK IN ID JP MY NZ PH SG KR TW TH VN AD AT BE BA BG HR CY CZ DK EE FI FR GE DE GI GR GL HU IS IE IL IT LV LI LT LU MK MT MD MC ME NL GB NO PL RO RU SM SK SI ZA ES SE CH TR VA)
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa master american_express discover verve]
@@ -294,10 +299,17 @@ def parse(body)
JSON.parse(body)
end
+ def url(action, url_override = nil)
+ if url_override.to_s == 'payment_redirect' && action == 'payments'
+ (self.test? ? self.payment_redirect_test : self.payment_redirect_live) + action.to_s
+ else
+ (self.test? ? self.test_url : self.live_url) + action.to_s
+ end
+ end
+
def commit(method, action, parameters)
- url = (test? ? test_url : live_url) + action.to_s
rel_path = "#{method}/v1/#{action}"
- response = api_request(method, url, rel_path, parameters)
+ response = api_request(method, url(action, @options[:url_override]), rel_path, parameters)
Response.new(
success_from(response),
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index ee76076e6eb..6299bce1362 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -3,7 +3,7 @@
class RemoteRapydTest < Test::Unit::TestCase
def setup
@gateway = RapydGateway.new(fixtures(:rapyd))
-
+ @gateway_payment_redirect = RapydGateway.new(fixtures(:rapyd).merge(url_override: 'payment_redirect'))
@amount = 100
@credit_card = credit_card('4111111111111111', first_name: 'Ryan', last_name: 'Reynolds', month: '12', year: '2035', verification_value: '345')
@declined_card = credit_card('4111111111111105')
@@ -413,4 +413,43 @@ def test_successful_purchase_nil_network_transaction_id
assert_success response
assert_equal 'SUCCESS', response.message
end
+
+ def test_successful_purchase_payment_redirect_url
+ response = @gateway_payment_redirect.purchase(@amount, @credit_card, @options.merge(pm_type: 'gb_visa_mo_card'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_3ds_v2_gateway_specific_payment_redirect_url
+ options = @options.merge(three_d_secure: { required: true })
+ options[:pm_type] = 'gb_visa_card'
+
+ response = @gateway_payment_redirect.purchase(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ end
+
+ def test_successful_purchase_without_cvv_payment_redirect_url
+ options = @options.merge({ pm_type: 'gb_visa_card', network_transaction_id: rand.to_s[2..11] })
+ @credit_card.verification_value = nil
+ response = @gateway_payment_redirect.purchase(100, @credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_refund_payment_redirect_url
+ purchase = @gateway_payment_redirect.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'SUCCESS', refund.message
+ end
+
+ def test_successful_subsequent_purchase_stored_credential_payment_redirect_url
+ response = @gateway_payment_redirect.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb
index 7223973d85e..bebdb0d17cb 100644
--- a/test/unit/gateways/rapyd_test.rb
+++ b/test/unit/gateways/rapyd_test.rb
@@ -5,6 +5,7 @@ class RapydTest < Test::Unit::TestCase
def setup
@gateway = RapydGateway.new(secret_key: 'secret_key', access_key: 'access_key')
+ @gateway_payment_redirect = RapydGateway.new(secret_key: 'secret_key', access_key: 'access_key', url_override: 'payment_redirect')
@credit_card = credit_card
@check = check
@amount = 100
@@ -476,6 +477,27 @@ def test_not_send_customer_object_for_recurring_transactions
end
end
+ def test_successful_purchase_for_payment_redirect_url
+ @gateway_payment_redirect.expects(:ssl_request).returns(successful_purchase_response)
+ response = @gateway_payment_redirect.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_use_proper_url_for_payment_redirect_url
+ url = @gateway_payment_redirect.send(:url, 'payments', 'payment_redirect')
+ assert_equal url, 'https://sandboxpayment-redirect.rapyd.net/v1/payments'
+ end
+
+ def test_use_proper_url_for_default_url
+ url = @gateway_payment_redirect.send(:url, 'payments')
+ assert_equal url, 'https://sandboxapi.rapyd.net/v1/payments'
+ end
+
+ def test_wrong_url_for_payment_redirect_url
+ url = @gateway_payment_redirect.send(:url, 'refund', 'payment_redirect')
+ assert_no_match %r{https://sandboxpayment-redirect.rapyd.net/v1/}, url
+ end
+
private
def pre_scrubbed
From 8d87c3393979bd6b49949f2f1fd18bc5793d56e0 Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Thu, 30 Nov 2023 15:29:06 -0500
Subject: [PATCH 245/390] Cecabank: Fix exemption_type when it is blank and
update the error code for some tests (#4968)
Summary:
------------------------------
Fix exemption_type when it is blank
Add exemption_type tests to increase the coverage
Update the error code for some tests
Replace ssl_request to ssl_post in the commit method
For Spreedly reference:
[SER-1009](https://spreedly.atlassian.net/browse/SER-1009)
Remote Test:
------------------------------
Finished in 63.024038 seconds.
19 tests, 65 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
0.30 tests/s, 1.03 assertions/s
Unit Tests:
------------------------------
Finished in 31.381399 seconds.
5739 tests, 78700 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
784 files inspected, no offenses detected
182.88 tests/s, 2507.86 assertions/s
Co-authored-by: Luis Urrea
---
CHANGELOG | 1 +
.../gateways/cecabank/cecabank_json.rb | 11 ++--
.../remote_cecabank_rest_json_test.rb | 9 ++-
test/unit/gateways/cecabank_rest_json_test.rb | 55 +++++++++++++++++++
4 files changed, 68 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 3318fa8101c..71760a2c0d5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -70,6 +70,7 @@
* Braintree: Correct issue in v2 stored credentials [aenand] #4967
* Stripe Payment Intents: Add the card brand field [yunnydang] #4964
* Rapyd: Enable new auth mode payment_redirect [javierpedrozaing] #4970
+* Cecabank: Fix exemption_type when it is blank and update the error code for some tests [sinourain] #4968
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
index 86c27e3db58..6dfff57ea35 100644
--- a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
@@ -26,8 +26,7 @@ class CecabankJsonGateway < Gateway
CECA_SCA_TYPES = {
low_value_exemption: :LOW,
- transaction_risk_analysis_exemption: :TRA,
- nil: :NONE
+ transaction_risk_analysis_exemption: :TRA
}.freeze
self.test_url = 'https://tpv.ceca.es/tpvweb/rest/procesos/'
@@ -164,7 +163,8 @@ def add_three_d_secure(post, options)
params = post[:parametros] ||= {}
return unless three_d_secure = options[:three_d_secure]
- params[:exencionSCA] ||= CECA_SCA_TYPES[options[:exemption_type]&.to_sym]
+ params[:exencionSCA] ||= CECA_SCA_TYPES.fetch(options[:exemption_type]&.to_sym, :NONE)
+
three_d_response = {
exemption_type: options[:exemption_type],
three_ds_version: three_d_secure[:version],
@@ -182,7 +182,7 @@ def add_three_d_secure(post, options)
params[:ThreeDsResponse] = three_d_response.to_json
end
- def commit(action, post, method = :post)
+ def commit(action, post)
auth_options = {
operation_number: post[:parametros][:numOperacion],
amount: post[:parametros][:importe]
@@ -193,8 +193,7 @@ def commit(action, post, method = :post)
params_encoded = encode_post_parameters(post)
add_signature(post, params_encoded, options)
-
- response = parse(ssl_request(method, url(action), post.to_json, headers))
+ response = parse(ssl_post(url(action), post.to_json, headers))
response[:parametros] = parse(response[:parametros]) if response[:parametros]
Response.new(
diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb
index 3abd1878553..76e3fcdf8f2 100644
--- a/test/remote/gateways/remote_cecabank_rest_json_test.rb
+++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb
@@ -33,7 +33,7 @@ def test_unsuccessful_authorize
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
assert_match '106900640', response.message
- assert_match '1-190', response.error_code
+ assert_match '190', response.error_code
end
def test_successful_capture
@@ -61,7 +61,7 @@ def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_match '106900640', response.message
- assert_match '1-190', response.error_code
+ assert_match '190', response.error_code
end
def test_successful_refund
@@ -156,6 +156,11 @@ def test_transcript_scrubbing
private
+ def get_response_params(transcript)
+ response = JSON.parse(transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"'))
+ JSON.parse(Base64.decode64(response['parametros']))
+ end
+
def three_d_secure
{
version: '2.2.0',
diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb
index 87ba73ef771..458864a0eb5 100644
--- a/test/unit/gateways/cecabank_rest_json_test.rb
+++ b/test/unit/gateways/cecabank_rest_json_test.rb
@@ -18,6 +18,16 @@ def setup
order_id: '1',
description: 'Store Purchase'
}
+
+ @three_d_secure = {
+ version: '2.2.0',
+ eci: '02',
+ cavv: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
+ ds_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
+ acs_transaction_id: '18c353b0-76e3-4a4c-8033-f14fe9ce39dc',
+ authentication_response_status: 'Y',
+ three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5'
+ }
end
def test_successful_authorize
@@ -110,6 +120,51 @@ def test_failed_void
assert response.test?
end
+ def test_purchase_without_exemption_type
+ @options[:exemption_type] = nil
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_nil three_d_secure['exemption_type']
+ assert_match params['exencionSCA'], 'NONE'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_low_value_exemption
+ @options[:exemption_type] = 'low_value_exemption'
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_match three_d_secure['exemption_type'], 'low_value_exemption'
+ assert_match params['exencionSCA'], 'LOW'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_transaction_risk_analysis_exemption
+ @options[:exemption_type] = 'transaction_risk_analysis_exemption'
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_match three_d_secure['exemption_type'], 'transaction_risk_analysis_exemption'
+ assert_match params['exencionSCA'], 'TRA'
+ end.respond_with(successful_purchase_response)
+ end
+
def test_transcript_scrubbing
assert_equal scrubbed_transcript, @gateway.scrub(transcript)
end
From c654c5c024ba3ceb665a16fc1c3846b8b2e9f948 Mon Sep 17 00:00:00 2001
From: Alma Malambo
Date: Thu, 30 Nov 2023 12:48:03 -0600
Subject: [PATCH 246/390] RedsysRest: Update to $0 verify
Unit:
21 tests, 91 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
16 tests, 45 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---
CHANGELOG | 1 +
.../billing/gateways/redsys_rest.rb | 17 +++++++++++-----
.../gateways/remote_redsys_rest_test.rb | 2 --
test/unit/gateways/redsys_rest_test.rb | 20 +++++++++----------
4 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index 71760a2c0d5..336d7c7997e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -71,6 +71,7 @@
* Stripe Payment Intents: Add the card brand field [yunnydang] #4964
* Rapyd: Enable new auth mode payment_redirect [javierpedrozaing] #4970
* Cecabank: Fix exemption_type when it is blank and update the error code for some tests [sinourain] #4968
+* RedsysRest: Update to $0 verify [almalee24] #4973
== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
diff --git a/lib/active_merchant/billing/gateways/redsys_rest.rb b/lib/active_merchant/billing/gateways/redsys_rest.rb
index 02758e797a7..ed265f1b28b 100644
--- a/lib/active_merchant/billing/gateways/redsys_rest.rb
+++ b/lib/active_merchant/billing/gateways/redsys_rest.rb
@@ -82,7 +82,8 @@ class RedsysRestGateway < Gateway
authorize: '1',
capture: '2',
refund: '3',
- cancel: '9'
+ cancel: '9',
+ verify: '7'
}
# These are the text meanings sent back by the acquirer when
@@ -249,10 +250,16 @@ def refund(money, authorization, options = {})
def verify(creditcard, options = {})
requires!(options, :order_id)
- MultiResponse.run(:use_first_response) do |r|
- r.process { authorize(100, creditcard, options) }
- r.process(:ignore_result) { void(r.authorization, options) }
- end
+ post = {}
+ add_action(post, :verify, options)
+ add_amount(post, 0, options)
+ add_order(post, options[:order_id])
+ add_payment(post, creditcard)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
end
def supports_scrubbing?
diff --git a/test/remote/gateways/remote_redsys_rest_test.rb b/test/remote/gateways/remote_redsys_rest_test.rb
index 44737703446..12df8399468 100644
--- a/test/remote/gateways/remote_redsys_rest_test.rb
+++ b/test/remote/gateways/remote_redsys_rest_test.rb
@@ -107,8 +107,6 @@ def test_successful_verify
assert_success response
assert_equal 'Transaction Approved', response.message
- assert_success response.responses.last, 'The void should succeed'
- assert_equal 'Cancellation Accepted', response.responses.last.message
end
def test_unsuccessful_verify
diff --git a/test/unit/gateways/redsys_rest_test.rb b/test/unit/gateways/redsys_rest_test.rb
index e8b9b631629..717ffeea341 100644
--- a/test/unit/gateways/redsys_rest_test.rb
+++ b/test/unit/gateways/redsys_rest_test.rb
@@ -155,23 +155,15 @@ def test_error_void
end
def test_successful_verify
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response)
+ @gateway.expects(:ssl_post).returns(successful_verify_response)
response = @gateway.verify(credit_card, @options)
assert_success response
end
- def test_successful_verify_with_failed_void
- @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(error_void_response)
- response = @gateway.verify(credit_card, @options)
- assert_success response
- assert_equal 'Transaction Approved', response.message
- end
-
def test_unsuccessful_verify
- @gateway.expects(:ssl_post).returns(failed_authorize_response)
+ @gateway.expects(:ssl_post).returns(failed_verify_response)
response = @gateway.verify(credit_card, @options)
assert_failure response
- assert_equal 'Refusal with no specific reason', response.message
end
def test_unknown_currency
@@ -211,6 +203,14 @@ def post_scrubbed
'
end
+ def successful_verify_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIwIiwiRHNfQ3VycmVuY3kiOiI5NzgiLCJEc19PcmRlciI6IjE3MDEzNjk0NzQ1NCIsIkRzX01lcmNoYW50Q29kZSI6Ijk5OTAwODg4MSIsIkRzX1Rlcm1pbmFsIjoiMjAxIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI1NDE4MTMiLCJEc19UcmFuc2FjdGlvblR5cGUiOiI3IiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19DYXJkTnVtYmVyIjoiNDU0ODgxKioqKioqMDAwNCIsIkRzX01lcmNoYW50RGF0YSI6IiIsIkRzX0NhcmRfQ291bnRyeSI6IjcyNCIsIkRzX0NhcmRfQnJhbmQiOiIxIiwiRHNfUHJvY2Vzc2VkUGF5TWV0aG9kIjoiMyIsIkRzX0NvbnRyb2xfMTcwMTM2OTQ3Njc2OCI6IjE3MDEzNjk0NzY3NjgifQ==\",\"Ds_Signature\":\"uoS0PJelg5_c4_7UgkYEJyatDuS3p2a-uJ3tB7SZPL4=\"}]
+ end
+
+ def failed_verify_response
+ %[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIwIiwiRHNfQ3VycmVuY3kiOiI5NzgiLCJEc19PcmRlciI6IjE3MDEzNjk2NDI4NyIsIkRzX01lcmNoYW50Q29kZSI6Ijk5OTAwODg4MSIsIkRzX1Rlcm1pbmFsIjoiMjAxIiwiRHNfUmVzcG9uc2UiOiIwMTkwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiIiLCJEc19UcmFuc2FjdGlvblR5cGUiOiI3IiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19DYXJkTnVtYmVyIjoiNDI0MjQyKioqKioqNDI0MiIsIkRzX01lcmNoYW50RGF0YSI6IiIsIkRzX0NhcmRfQ291bnRyeSI6IjgyNiIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE3MDEzNjk2NDUxMjIiOiIxNzAxMzY5NjQ1MTIyIn0=\",\"Ds_Signature\":\"oaS6-Zuz6v6l-Jgs5hKDZ0tn01W9Z3gKNfhmfAGdfMo=\"}]
+ end
+
def successful_purchase_response
%[{\"Ds_SignatureVersion\":\"HMAC_SHA256_V1\",\"Ds_MerchantParameters\":\"eyJEc19BbW91bnQiOiIxMDAiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTY0NTEzMjI0MDE5IiwiRHNfTWVyY2hhbnRDb2RlIjoiMzI3MjM0Njg4IiwiRHNfVGVybWluYWwiOiIzIiwiRHNfUmVzcG9uc2UiOiIwMDAwIiwiRHNfQXV0aG9yaXNhdGlvbkNvZGUiOiI0ODgxODUiLCJEc19UcmFuc2FjdGlvblR5cGUiOiIwIiwiRHNfU2VjdXJlUGF5bWVudCI6IjAiLCJEc19MYW5ndWFnZSI6IjEiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSIsIkRzX1Byb2Nlc3NlZFBheU1ldGhvZCI6IjMiLCJEc19Db250cm9sXzE2NDUxMzIyNDE0NDkiOiIxNjQ1MTMyMjQxNDQ5In0=\",\"Ds_Signature\":\"63UXUOSVheJiBWxaWKih5yaVvfOSeOXAuoRUZyHBwJo=\"}]
end
From a38ce2bca28039b969fe48fe5acf9f7e1a77952a Mon Sep 17 00:00:00 2001
From: Luis Mario Urrea Murillo
Date: Fri, 1 Dec 2023 13:55:08 -0500
Subject: [PATCH 247/390] CommerceHub: Add credit transaction (#4965)
Description:
------------------------------
Add credit transaction for CommerceHub gateway. Some remote tests fails because the credentials with which the test was taken do not have some permissions (e.g. store). However, it was tested with other credentials and these cases work.
For Spreedly reference:
[SER-924](https://spreedly.atlassian.net/browse/SER-924)
Remote Test:
------------------------------
Finished in 45.018883 seconds.
29 tests, 71 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
82.7586% passed
0.64 tests/s, 1.58 assertions/s
Unit Tests:
------------------------------
Finished in 30.351561 seconds.
5740 tests, 78705 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
RuboCop:
------------------------------
784 files inspected, no offenses detected
189.12 tests/s, 2593.11 assertions/s
Co-authored-by: Luis Urrea