Skip to content

Commit

Permalink
Nuvei: Add ACH support (#5269)
Browse files Browse the repository at this point in the history
* Nuvei: Add ACH support

Description
-------------------------
[SER-1403](https://spreedly.atlassian.net/browse/SER-1403)

This commit add ACH transaction for Nuvei

Unit test
-------------------------
Finished in 0.010487 seconds

10 tests, 34 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

53.56 tests/s, 3242.11 assertions/s

Remote test
-------------------------
Finished in 36.783565 seconds.

18 tests, 48 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

0.49 tests/s, 1.30 assertions/s

Rubocop
-------------------------
801 files inspected, no offenses detected

* Remove routingNumber from scrub

---------

Co-authored-by: Javier Pedroza <[email protected]>
Co-authored-by: Nick <[email protected]>
  • Loading branch information
3 people authored Oct 18, 2024
1 parent 82a6fa1 commit 74848ea
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
* StripePI: Update to retrieve_setup_intent and headers [almalee24] #5283
* Upgrade rexml to 3.3.8 [raymzag] #5245
* Nuvei: Add partial approval feature [javierpedrozaing] #5250
* Nuvei: Add ACH support [javierpedrozaing] #5269

== Version 1.137.0 (August 2, 2024)
* Unlock dependency on `rexml` to allow fixing a CVE (#5181).
Expand Down
38 changes: 30 additions & 8 deletions lib/active_merchant/billing/gateways/nuvei.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def credit(money, payment, options = {})

build_post_data(post)
add_amount(post, money, options)
add_payment_method(post, payment, :cardData)
add_payment_method(post, payment, :cardData, options)
add_address(post, payment, options)
add_customer_ip(post, options)

Expand Down Expand Up @@ -149,7 +149,8 @@ def scrub(transcript)
gsub(%r(("cardCvv\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("merchantId\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("merchantSiteId\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("merchantKey\\?":\\?")\d+), '\1[FILTERED]')
gsub(%r(("merchantKey\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("accountNumber\\?":\\?")\d+), '\1[FILTERED]')
end

private
Expand All @@ -164,6 +165,12 @@ def add_customer_ip(post, options)
post[:deviceDetails] = { ipAddress: options[:ip] }
end

def url_details(post, options)
return unless options[:notification_url]

post[:urlDetails] = { notificationUrl: options[:notification_url] }
end

def add_amount(post, money, options)
post[:amount] = amount(money)
post[:currency] = (options[:currency] || currency(money))
Expand All @@ -181,11 +188,30 @@ def credit_card_hash(payment)
}
end

def add_payment_method(post, payment, key, options = {})
def get_last_four_digits(number)
number[-4..-1]
end

def add_bank_account(post, payment, options)
post[:paymentOption] = {
alternativePaymentMethod: {
paymentMethod: 'apmgw_ACH',
AccountNumber: payment.account_number,
RoutingNumber: payment.routing_number,
classic_ach_account_type: options[:account_type]
}
}
end

def add_payment_method(post, payment, key = :paymentOption, options = {})
payment_data = payment.is_a?(CreditCard) ? credit_card_hash(payment) : payment

if payment.is_a?(CreditCard)
post[key] = key == :paymentOption ? { card: payment_data } : payment_data
elsif payment.is_a?(Check)
post[:userTokenId] = options[:user_token_id]
add_bank_account(post, payment, options)
url_details(post, options)
else
post[key] = {
userPaymentOptionId: payment_data,
Expand All @@ -194,10 +220,6 @@ def add_payment_method(post, payment, key, options = {})
end
end

def get_last_four_digits(number)
number[-4..-1]
end

def add_customer_names(full_name, payment_method)
split_names(full_name).tap do |names|
names[0] = payment_method&.first_name unless names[0].present? || payment_method.is_a?(String)
Expand Down Expand Up @@ -380,7 +402,7 @@ def parse(body)
end

def success_from(response)
response[:status] == 'SUCCESS' && %w[APPROVED REDIRECT].include?(response[:transactionStatus])
response[:status] == 'SUCCESS' && %w[APPROVED REDIRECT PENDING].include?(response[:transactionStatus])
end

def authorization_from(action, response, post)
Expand Down
9 changes: 9 additions & 0 deletions test/remote/gateways/remote_nuvei_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def setup
}
}
}

@bank_account = check(account_number: '111111111', routing_number: '999999992')
end

def test_transcript_scrubbing
Expand Down Expand Up @@ -277,4 +279,11 @@ def test_successful_partial_approval
assert_equal '55', response.params['partialApproval']['processedAmount']
assert_match 'APPROVED', response.message
end

def test_successful_authorize_with_bank_account
@options.update(billing_address: address.merge(country: 'US', state: 'MA'))
response = @gateway.authorize(1.25, @bank_account, @options)
assert_success response
assert_match 'PENDING', response.message
end
end
15 changes: 15 additions & 0 deletions test/unit/gateways/nuvei_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def setup
relatedTransactionId: 'test_related_transaction_id',
timeStamp: 'test_time_stamp'
}

@bank_account = check()
end

def test_calculate_checksum_authenticate
Expand Down Expand Up @@ -262,6 +264,19 @@ def test_successful_stored_credentials_merchant_recurring
end.respond_with(successful_purchase_response)
end

def test_successful_authorize_bank_account
stub_comms(@gateway, :ssl_request) do
@gateway.authorize(1.25, @bank_account, @options)
end.check_request(skip_response: true) do |_method, endpoint, data, _headers|
json_data = JSON.parse(data)
if /payment/.match?(endpoint)
assert_equal('apmgw_ACH', json_data['paymentOption']['alternativePaymentMethod']['paymentMethod'])
assert_match(/#{@bank_account.routing_number}/, json_data['paymentOption']['alternativePaymentMethod']['RoutingNumber'])
assert_match(/#{@bank_account.account_number}/, json_data['paymentOption']['alternativePaymentMethod']['AccountNumber'])
end
end
end

private

def three_ds_assertions(payment_option_card)
Expand Down

0 comments on commit 74848ea

Please sign in to comment.