Skip to content

Commit

Permalink
Nuvei: Adding basic operations
Browse files Browse the repository at this point in the history
Nuvei: Base Gateway Layout

Description
-------------------------

[SER-1349](https://spreedly.atlassian.net/browse/SER-1349)
[SER-1350](https://spreedly.atlassian.net/browse/SER-1350)
[SER-1348](https://spreedly.atlassian.net/browse/SER-1348)

This commit add the basic operation for Nuvei such as Purchase, refund, void and general credit

Unit test
-------------------------
Finished in 68.576328 seconds.
6003 tests, 80235 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed

Remote test
-------------------------
Finished in 37.911283 seconds.
17 tests, 46 assertions, 0 failures, 0 errors, 0 pendings,
0 omissions, 0 notifications
100% passed
0.45 tests/s, 1.21 assertions/s

Rubocop
-------------------------
801 files inspected, no offenses detected
  • Loading branch information
Heavyblade committed Aug 26, 2024
1 parent 1549bec commit a3b747a
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 59 deletions.
73 changes: 51 additions & 22 deletions lib/active_merchant/billing/gateways/nuvei.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ def initialize(options = {})
fetch_session_token unless session_token_valid?
end

def authorize(money, payment, options = {})
post = { transactionType: 'Auth' }
def authorize(money, payment, options = {}, transaction_type = 'Auth')
post = { transactionType: transaction_type }

build_post_data(post, :authorize)
build_post_data(post)
add_amount(post, money, options)
add_payment_method(post, payment)
add_address(post, payment, options)
Expand All @@ -39,22 +39,53 @@ def authorize(money, payment, options = {})
commit(:purchase, post)
end

def purchase(money, payment, options = {}); end
def purchase(money, payment, options = {})
authorize(money, payment, options, 'Sale')
end

def capture(money, authorization, options = {})
post = { relatedTransactionId: authorization }

build_post_data(post, :capture)
build_post_data(post)
add_amount(post, money, options)

commit(:capture, post)
end

def refund(money, authorization, options = {}); end
def refund(money, authorization, options = {})
post = { relatedTransactionId: authorization }

build_post_data(post)
add_amount(post, money, options)

commit(:refund, post)
end

def void(authorization, options = {}); end
def void(authorization, options = {})
post = { relatedTransactionId: authorization }
build_post_data(post)

def credit(money, payment, options = {}); end
commit(:void, post)
end

def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(0, credit_card, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end

def credit(money, payment, options = {})
post = { userTokenId: options[:user_token_id] }

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

commit(:general_credit, post.compact)
end

def supports_scrubbing?
true
Expand Down Expand Up @@ -93,12 +124,9 @@ def credit_card_hash(payment)
}
end

def add_payment_method(post, payment)
if payment.is_a?(CreditCard)
post[:paymentOption] = { card: credit_card_hash(payment) }
else
post[:paymentOption] = { card: { cardToken: payment } }
end
def add_payment_method(post, payment, key = :paymentOption)
payment_data = payment.is_a?(CreditCard) ? credit_card_hash(payment) : { cardToken: payment }
post[key] = key == :cardData ? payment_data : { card: payment_data }
end

def add_customer_names(full_name, payment_method)
Expand Down Expand Up @@ -126,7 +154,7 @@ def current_timestamp
Time.now.utc.strftime('%Y%m%d%H%M%S')
end

def build_post_data(post, action)
def build_post_data(post)
post[:merchantId] = @options[:merchant_id]
post[:merchantSiteId] = @options[:merchant_site_id]
post[:timeStamp] = current_timestamp.to_i
Expand All @@ -139,7 +167,7 @@ def calculate_checksum(post, action)
keys = case action
when :authenticate
[:timeStamp]
when :capture
when :capture, :refund, :void
%i[clientUniqueId amount currency relatedTransactionId timeStamp]
else
%i[amount currency timeStamp]
Expand All @@ -161,12 +189,12 @@ def send_session_request(post)
message_from(response),
response,
test: test?,
error_code: response[:errCode]
error_code: error_code_from(response)
)
end

def fetch_session_token(post = {})
build_post_data(post, :authenticate)
build_post_data(post)
send_session_request(post)
end

Expand All @@ -188,7 +216,7 @@ def commit(action, post, authorization = nil, method = :post)
response,
authorization: authorization_from(action, response, post),
test: test?,
error_code: error_code_from(action, response)
error_code: error_code_from(response)
)
rescue ResponseError => e
response = parse(e.response.body)
Expand All @@ -201,8 +229,8 @@ def url(action, id = nil)
"#{test? ? test_url : live_url}#{ENDPOINTS_MAPPING[action] % id}"
end

def error_code_from(action, response)
(response[:statusName] || response[:status]) unless success_from(response)
def error_code_from(response)
response[:errCode] == 0 ? response[:gwErrorCode] : response[:errCode]
end

def headers
Expand Down Expand Up @@ -231,7 +259,8 @@ def authorization_from(action, response, post)
end

def message_from(response)
response[:status]
reason = response[:reason]&.present? ? response[:reason] : nil
response[:gwErrorReason] || reason || response[:transactionStatus]
end
end
end
Expand Down
119 changes: 82 additions & 37 deletions test/remote/gateways/remote_nuvei_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,8 @@ def setup
@options = {
email: 'test@gmail.com',
billing_address: address.merge(name: 'Cure Tester'),
ip_address: '127.0.0.1'
ip: '127.0.0.1'
}

@post = {
merchantId: 'test_merchant_id',
merchantSiteId: 'test_merchant_site_id',
clientRequestId: 'test_client_request_id',
amount: 'test_amount',
currency: 'test_currency',
timeStamp: 'test_time_stamp'
}
end

def test_calculate_checksum
expected_checksum = Digest::SHA256.hexdigest("test_merchant_idtest_merchant_site_idtest_client_request_idtest_amounttest_currencytest_time_stamp#{@gateway.options[:secret_key]}")
assert_equal expected_checksum, @gateway.send(:calculate_checksum, @post, :purchase)
end

def test_calculate_checksum_authenticate
expected_checksum = Digest::SHA256.hexdigest("test_merchant_idtest_merchant_site_idtest_client_request_idtest_time_stamp#{@gateway.options[:secret_key]}")
@post.delete(:amount)
@post.delete(:currency)
assert_equal expected_checksum, @gateway.send(:calculate_checksum, @post, :authenticate)
end

def test_calculate_checksum_capture
expected_checksum = Digest::SHA256.hexdigest("test_merchant_idtest_merchant_site_idtest_client_request_idtest_client_idtest_amounttest_currencytest_transaction_idtest_time_stamp#{@gateway.options[:secret_key]}")
@post[:clientUniqueId] = 'test_client_id'
@post[:relatedTransactionId] = 'test_transaction_id'
assert_equal expected_checksum, @gateway.send(:calculate_checksum, @post, :capture)
end

def test_transcript_scrubbing
Expand All @@ -62,16 +34,14 @@ def test_failed_session_token_generation
@gateway.options[:merchant_site_id] = 123
response = @gateway.send(:fetch_session_token, {})
assert_failure response
assert_match 'ERROR', response.message
assert_match 'Invalid merchant site id', response.params['reason']
assert_match 'Invalid merchant site id', response.message
end

def test_successful_authorize
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_not_nil response.params[:transactionId]
assert_match 'SUCCESS', response.message
assert_match 'APPROVED', response.params['transactionStatus']
assert_match 'APPROVED', response.message
end

def test_failed_authorize
Expand All @@ -87,14 +57,89 @@ def test_successful_authorize_and_capture
capture_response = @gateway.capture(@amount, response.authorization)

assert_success capture_response
assert_match 'SUCCESS', capture_response.message
assert_match 'APPROVED', capture_response.params['transactionStatus']
assert_match 'APPROVED', capture_response.message
end

def test_successful_zero_auth
response = @gateway.authorize(0, @credit_card, @options)
assert_success response
assert_match 'SUCCESS', response.message
assert_match 'APPROVED', response.params['transactionStatus']
assert_match 'APPROVED', response.message
end

def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_not_nil response.params[:transactionId]
assert_match 'APPROVED', response.message
assert_match 'SUCCESS', response.params['status']
end

def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_match 'DECLINED', response.params['transactionStatus']
end

def test_failed_purchase_with_invalid_cvv
@credit_card.verification_value = nil
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_match 'ERROR', response.params['transactionStatus']
assert_match 'Invalid CVV2', response.message
end

def test_failed_capture_invalid_transaction_id
response = @gateway.capture(@amount, '123')
assert_failure response
assert_match 'ERROR', response.params['status']
assert_match 'Invalid relatedTransactionId', response.message
end

def test_successful_void
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response

void_response = @gateway.void(response.authorization)
assert_success void_response
assert_match 'SUCCESS', void_response.params['status']
assert_match 'APPROVED', void_response.message
end

def test_failed_void_invalid_transaction_id
response = @gateway.void('123')
assert_failure response
assert_match 'ERROR', response.params['status']
assert_match 'Invalid relatedTransactionId', response.message
end

def test_successful_refund
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response

refund_response = @gateway.refund(@amount, response.authorization)
assert_success refund_response
assert_match 'SUCCESS', refund_response.params['status']
assert_match 'APPROVED', refund_response.message
end

def test_successful_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
assert_match 'SUCCESS', response.params['status']
assert_match 'APPROVED', response.message
end

def test_successful_general_credit
credit_response = @gateway.credit(@amount, @credit_card, @options.merge!(user_token_id: '123'))
assert_success credit_response
assert_match 'SUCCESS', credit_response.params['status']
assert_match 'APPROVED', credit_response.message
end

def test_failed_general_credit
credit_response = @gateway.credit(@amount, @declined_card, @options)
assert_failure credit_response
assert_match 'ERROR', credit_response.params['status']
assert_match 'Invalid user token', credit_response.message
end
end
44 changes: 44 additions & 0 deletions test/unit/gateways/nuvei_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,44 @@ def test_successful_authorize
end.respond_with(successful_authorize_response)
end

def test_successful_purchase
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_method, endpoint, data, _headers|
if /payment/.match?(endpoint)
json_data = JSON.parse(data)
assert_match(/#{@amount}/, json_data['amount'])
assert_match(/#{@credit_card.number}/, json_data['paymentOption']['card']['cardNumber'])
assert_match(/#{@credit_card.verification_value}/, json_data['paymentOption']['card']['CVV'])
assert_match(%r(/payment), endpoint)
end
end.respond_with(successful_purchase_response)
end

def test_successful_refund
stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, '123456', @options)
end.check_request(skip_response: true) do |_method, endpoint, data, _headers|
json_data = JSON.parse(data)
if /refundTransaction/.match?(endpoint)
assert_match(/123456/, json_data['relatedTransactionId'])
assert_match(/#{@amount}/, json_data['amount'])
end
end
end

def test_successful_credit
stub_comms(@gateway, :ssl_request) do
@gateway.credit(@amount, @credit_card, @options)
end.check_request do |_method, endpoint, data, _headers|
json_data = JSON.parse(data)
if /payout/.match?(endpoint)
assert_match(/#{@amount}/, json_data['amount'])
assert_match(/#{@credit_card.number}/, json_data['cardData']['cardNumber'])
end
end.respond_with(successful_purchase_response)
end

private

def pre_scrubbed
Expand Down Expand Up @@ -140,4 +178,10 @@ def successful_authorize_response
{"internalRequestId":1171104468,"status":"SUCCESS","errCode":0,"reason":"","merchantId":"3755516963854600967","merchantSiteId":"255388","version":"1.0","clientRequestId":"02ba666c-e3e5-4ec9-ae30-3f8500b18c96","sessionToken":"29226538-82c7-4a3c-b363-cb6829b8c32a","clientUniqueId":"c00ed73a7d682bf478295d57bdae3028","orderId":"471361708","paymentOption":{"userPaymentOptionId":"","card":{"ccCardNumber":"4****1390","bin":"476134","last4Digits":"1390","ccExpMonth":"09","ccExpYear":"25","acquirerId":"19","cvv2Reply":"","avsCode":"","cardType":"Debit","cardBrand":"VISA","issuerBankName":"INTL HDQTRS-CENTER OWNED","issuerCountry":"SG","isPrepaid":"false","threeD":{},"processedBrand":"VISA"},"paymentAccountReference":"f4iK2pnudYKvTALGdcwEzqj9p4"},"transactionStatus":"APPROVED","gwErrorCode":0,"gwExtendedErrorCode":0,"issuerDeclineCode":"","issuerDeclineReason":"","transactionType":"Auth","transactionId":"7110000000001908486","externalTransactionId":"","authCode":"111397","customData":"","fraudDetails":{"finalDecision":"Accept","score":"0"},"externalSchemeTransactionId":"","merchantAdviceCode":""}
RESPONSE
end

def successful_purchase_response
<<~RESPONSE
{"internalRequestId":1172848838, "status":"SUCCESS", "errCode":0, "reason":"", "merchantId":"3755516963854600967", "merchantSiteId":"255388", "version":"1.0", "clientRequestId":"a114381a-0f88-46d0-920c-7b5614f29e5b", "sessionToken":"d3424c9c-dd6d-40dc-85da-a2b92107cbe3", "clientUniqueId":"3ba2a81c46d78837ea819d9f3fe644e7", "orderId":"471833818", "paymentOption":{"userPaymentOptionId":"", "card":{"ccCardNumber":"4****1390", "bin":"476134", "last4Digits":"1390", "ccExpMonth":"09", "ccExpYear":"25", "acquirerId":"19", "cvv2Reply":"", "avsCode":"", "cardType":"Debit", "cardBrand":"VISA", "issuerBankName":"INTL HDQTRS-CENTER OWNED", "issuerCountry":"SG", "isPrepaid":"false", "threeD":{}, "processedBrand":"VISA"}, "paymentAccountReference":"f4iK2pnudYKvTALGdcwEzqj9p4"}, "transactionStatus":"APPROVED", "gwErrorCode":0, "gwExtendedErrorCode":0, "issuerDeclineCode":"", "issuerDeclineReason":"", "transactionType":"Sale", "transactionId":"7110000000001990927", "externalTransactionId":"", "authCode":"111711", "customData":"", "fraudDetails":{"finalDecision":"Accept", "score":"0"}, "externalSchemeTransactionId":"", "merchantAdviceCode":""}
RESPONSE
end
end

0 comments on commit a3b747a

Please sign in to comment.