diff --git a/CHANGELOG b/CHANGELOG index e57784cb91b..356ba12d824 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -65,6 +65,7 @@ * Nuvei: Add partial approval feature [javierpedrozaing] #5250 * Nuvei: Add ACH support [javierpedrozaing] #5269 * Nuvei: Add GSF for verify method [javierpedrozaing] #5278 +* Nuvei: Add Google and Apple pay [javierpedrozaing] #5289 == Version 1.137.0 (August 2, 2024) * Unlock dependency on `rexml` to allow fixing a CVE (#5181). diff --git a/lib/active_merchant/billing/gateways/nuvei.rb b/lib/active_merchant/billing/gateways/nuvei.rb index 6e232ce6215..5121c776eb6 100644 --- a/lib/active_merchant/billing/gateways/nuvei.rb +++ b/lib/active_merchant/billing/gateways/nuvei.rb @@ -22,6 +22,11 @@ class NuveiGateway < Gateway init_payment: '/initPayment' } + NETWORK_TOKENIZATION_CARD_MAPPING = { + 'apple_pay' => 'ApplePay', + 'google_pay' => 'GooglePay' + } + def initialize(options = {}) requires!(options, :merchant_id, :merchant_site_id, :secret_key) super @@ -150,7 +155,8 @@ def scrub(transcript) gsub(%r(("merchantId\\?":\\?")\d+), '\1[FILTERED]'). gsub(%r(("merchantSiteId\\?":\\?")\d+), '\1[FILTERED]'). gsub(%r(("merchantKey\\?":\\?")\d+), '\1[FILTERED]'). - gsub(%r(("accountNumber\\?":\\?")\d+), '\1[FILTERED]') + gsub(%r(("accountNumber\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cryptogram\\?":\\?")[^"\\]*)i, '\1[FILTERED]') end private @@ -203,10 +209,21 @@ def add_bank_account(post, payment, options) } end - def add_payment_method(post, payment, key = :paymentOption, options = {}) - payment_data = payment.is_a?(CreditCard) ? credit_card_hash(payment) : payment + def add_payment_method(post, payment, key, options = {}) + payment_data = payment.is_a?(CreditCard) || payment.is_a?(NetworkTokenizationCreditCard) ? credit_card_hash(payment) : payment + if payment.is_a?(NetworkTokenizationCreditCard) + payment_data[:brand] = payment.brand.upcase + + external_token = {} + external_token[:externalTokenProvider] = NETWORK_TOKENIZATION_CARD_MAPPING[payment.source.to_s] + external_token[:cryptogram] = payment.payment_cryptogram if payment.payment_cryptogram + external_token[:eciProvider] = payment.eci if payment.eci + + payment_data.slice!(:cardNumber, :expirationMonth, :expirationYear, :last4Digits, :brand, :CVV) + + post[:paymentOption] = { card: payment_data.merge(externalToken: external_token) } - if payment.is_a?(CreditCard) + elsif payment.is_a?(CreditCard) post[key] = key == :paymentOption ? { card: payment_data } : payment_data elsif payment.is_a?(Check) post[:userTokenId] = options[:user_token_id] diff --git a/test/remote/gateways/remote_nuvei_test.rb b/test/remote/gateways/remote_nuvei_test.rb index 92086835b7e..30a840d1175 100644 --- a/test/remote/gateways/remote_nuvei_test.rb +++ b/test/remote/gateways/remote_nuvei_test.rb @@ -40,6 +40,25 @@ def setup } @bank_account = check(account_number: '111111111', routing_number: '999999992') + + @apple_pay_card = network_tokenization_credit_card( + '5204245250460049', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '12', + year: Time.new.year + 2, + source: :apple_pay, + verification_value: 111, + eci: '5' + ) + + @google_pay_card = network_tokenization_credit_card( + '4761344136141390', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '12', + year: Time.new.year + 2, + source: :google_pay, + eci: '5' + ) end def test_transcript_scrubbing @@ -292,4 +311,18 @@ def test_successful_authorize_with_bank_account assert_success response assert_match 'PENDING', response.message end + + def test_successful_purchase_with_apple_pay + response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.params[:paymentOption][:userPaymentOptionId] + end + + def test_successful_purchase_with_google_pay + response = @gateway.purchase(@amount, @google_pay_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.params[:paymentOption][:userPaymentOptionId] + end end diff --git a/test/unit/gateways/nuvei_test.rb b/test/unit/gateways/nuvei_test.rb index 2d70ef66f59..1120356452b 100644 --- a/test/unit/gateways/nuvei_test.rb +++ b/test/unit/gateways/nuvei_test.rb @@ -52,6 +52,26 @@ def setup } @bank_account = check() + + @apple_pay_card = network_tokenization_credit_card( + '5204 2452 5046 0049', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '12', + year: Time.new.year, + source: :apple_pay, + verification_value: 111, + eci: '5' + ) + + @google_pay_card = network_tokenization_credit_card( + '4761209980011439', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '11', + year: '2022', + source: :google_pay, + verification_value: 111, + eci: '5' + ) end def test_calculate_checksum_authenticate @@ -291,6 +311,30 @@ def test_successful_verify end end + def test_successful_purchase_with_apple_pay + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @apple_pay_card, @options) + end.check_request do |_method, endpoint, data, _headers| + if /payment/.match?(endpoint) + json_data = JSON.parse(data) + assert_equal 'ApplePay', json_data['paymentOption']['card']['externalToken']['externalTokenProvider'] + assert_not_nil json_data['paymentOption']['card']['externalToken']['cryptogram'] + end + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_google_pay + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @google_pay_card, @options) + end.check_request do |_method, endpoint, data, _headers| + if /payment/.match?(endpoint) + json_data = JSON.parse(data) + assert_equal 'GooglePay', json_data['paymentOption']['card']['externalToken']['externalTokenProvider'] + assert_not_nil json_data['paymentOption']['card']['externalToken']['cryptogram'] + end + end.respond_with(successful_purchase_response) + end + private def three_ds_assertions(payment_option_card)