Skip to content

Commit

Permalink
Cybersource Rest: Support NT (#5107)
Browse files Browse the repository at this point in the history
* Cybersource Rest: Support NT

COMP-75

Adds support for Network Tokens on the Cybersource
Rest gateway.

Test Summary
Local:
5848 tests, 79228 assertions, 0 failures, 23 errors, 0 pendings, 0 omissions, 0 notifications
99.6067% passed
Errors unrelated to this gateway
Unit:
32 tests, 160 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
50 tests, 157 assertions, 7 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
86% passed
Errors also on master

* PR feedback

* pr feedback

* scrub cryptogram

* changelog
  • Loading branch information
aenand committed May 3, 2024
1 parent f89d548 commit 0faace6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Expand Up @@ -160,6 +160,7 @@
* CyberSource: Update NT flow [almalee24] #5106
* Litle: Update enhanced data fields to pass integers [yunnydang] #5113
* Litle: Update commodity code and line item total fields [yunnydang] #5115
* Cybersource Rest: Add support for network tokens [aenand] #5107


== Version 1.135.0 (August 24, 2023)
Expand Down
33 changes: 22 additions & 11 deletions lib/active_merchant/billing/gateways/cyber_source_rest.rb
Expand Up @@ -31,11 +31,16 @@ class CyberSourceRestGateway < Gateway
visa: '001'
}

PAYMENT_SOLUTION = {
WALLET_PAYMENT_SOLUTION = {
apple_pay: '001',
google_pay: '012'
}

NT_PAYMENT_SOLUTION = {
'master' => '014',
'visa' => '015'
}

def initialize(options = {})
requires!(options, :merchant_id, :public_key, :private_key)
super
Expand Down Expand Up @@ -93,6 +98,7 @@ def scrub(transcript)
gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]').
gsub(/(\\?"routingNumber\\?":\\?")\d+/, '\1[FILTERED]').
gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]').
gsub(/(\\?"cryptogram\\?":\\?")[^<]+/, '\1[FILTERED]').
gsub(/(signature=")[^"]*/, '\1[FILTERED]').
gsub(/(keyid=")[^"]*/, '\1[FILTERED]').
gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]')
Expand Down Expand Up @@ -216,25 +222,30 @@ def add_payment(post, payment, options)
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[:processingInformation][:commerceIndicator] = 'internet' unless options[:stored_credential] || 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]
type: CREDIT_CARD_CODES[card_brand(payment).to_sym],
transactionType: payment.source == :network_token ? '3' : '1'
}

if card_brand(payment) == 'master'
post[:consumerAuthenticationInformation] = {
ucafAuthenticationData: payment.payment_cryptogram,
ucafCollectionIndicator: '2'
}
if payment.source == :network_token && NT_PAYMENT_SOLUTION[payment.brand]
post[:processingInformation][:paymentSolution] = NT_PAYMENT_SOLUTION[payment.brand]
else
post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram }
# Apple Pay / Google Pay
post[:processingInformation][:paymentSolution] = WALLET_PAYMENT_SOLUTION[payment.source]
if card_brand(payment) == 'master'
post[:consumerAuthenticationInformation] = {
ucafAuthenticationData: payment.payment_cryptogram,
ucafCollectionIndicator: '2'
}
else
post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram }
end
end
end

Expand Down
47 changes: 47 additions & 0 deletions test/remote/gateways/remote_cyber_source_rest_test.rb
Expand Up @@ -13,6 +13,29 @@ def setup
@master_card = credit_card('2222420000001113', brand: 'master')
@discover_card = credit_card('6011111111111117', brand: 'discover')

@visa_network_token = network_tokenization_credit_card(
'4111111111111111',
brand: 'visa',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
source: :network_token
)
@amex_network_token = network_tokenization_credit_card(
'378282246310005',
brand: 'american_express',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
source: :network_token
)

@mastercard_network_token = network_tokenization_credit_card(
'5555555555554444',
brand: 'master',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
source: :network_token
)

@apple_pay = network_tokenization_credit_card(
'4111111111111111',
payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
Expand Down Expand Up @@ -301,6 +324,30 @@ def test_failure_verify
assert_equal 'INVALID_ACCOUNT', response.error_code
end

def test_successful_authorize_with_visa_network_token
response = @gateway.authorize(@amount, @visa_network_token, @options)

assert_success response
assert_equal 'AUTHORIZED', response.message
refute_empty response.params['_links']['capture']
end

def test_successful_authorize_with_mastercard_network_token
response = @gateway.authorize(@amount, @mastercard_network_token, @options)

assert_success response
assert_equal 'AUTHORIZED', response.message
refute_empty response.params['_links']['capture']
end

def test_successful_authorize_with_amex_network_token
response = @gateway.authorize(@amount, @amex_network_token, @options)

assert_success response
assert_equal 'AUTHORIZED', response.message
refute_empty response.params['_links']['capture']
end

def test_successful_authorize_with_apple_pay
response = @gateway.authorize(@amount, @apple_pay, @options)

Expand Down
86 changes: 86 additions & 0 deletions test/unit/gateways/cyber_source_rest_test.rb
Expand Up @@ -17,6 +17,22 @@ def setup
year: 2031
)
@master_card = credit_card('2222420000001113', brand: 'master')

@visa_network_token = network_tokenization_credit_card(
'4111111111111111',
brand: 'visa',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
source: :network_token
)

@mastercard_network_token = network_tokenization_credit_card(
'5555555555554444',
brand: 'master',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
source: :network_token
)
@apple_pay = network_tokenization_credit_card(
'4111111111111111',
payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
Expand Down Expand Up @@ -188,6 +204,34 @@ def test_add_shipping_address
assert_equal '4158880000', address[:phoneNumber]
end

def test_authorize_network_token_visa
stub_comms do
@gateway.authorize(100, @visa_network_token, @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
assert_equal '015', request['processingInformation']['paymentSolution']
assert_equal 'internet', request['processingInformation']['commerceIndicator']
end.respond_with(successful_purchase_response)
end

def test_authorize_network_token_mastercard
stub_comms do
@gateway.authorize(100, @mastercard_network_token, @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal '002', request['paymentInformation']['tokenizedCard']['type']
assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
assert_equal '014', request['processingInformation']['paymentSolution']
assert_equal 'internet', request['processingInformation']['commerceIndicator']
end.respond_with(successful_purchase_response)
end

def test_authorize_apple_pay_visa
stub_comms do
@gateway.authorize(100, @apple_pay, @options)
Expand Down Expand Up @@ -495,6 +539,48 @@ def post_scrubbed
POST
end

def pre_scrubbed_nt
<<-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\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"paymentInformation\":{\"tokenizedCard\":{\"number\":\"4111111111111111\",\"expirationMonth\":9,\"expirationYear\":2025,\"cryptogram\":\"EHuWW9PiBkWvqE5juRwDzAUFBAk=\",\"type\":\"001\",\"transactionType\":\"3\"}},\"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\"}},\"processingInformation\":{\"commerceIndicator\":\"internet\",\"paymentSolution\":\"015\",\"authorizationOptions\":{}}}"
-> "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/7145981349676498704951/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/7145981349676498704951\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/captures\"}},\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"id\":\"7145981349676498704951\",\"issuerInformation\":{\"responseRaw\":\"0110322000000E10000200000000000001022105012115353420253130383141564D334B5953323833313030303030000159008000223134573031363135303730333830323039344730363400103232415050524F56414C00065649435243200034544B54523031313132313231323132313231544C3030323636504E30303431313131\"},\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"requestorId\":\"12121212121\",\"assuranceLevel\":\"66\",\"type\":\"001\"},\"card\":{\"suffix\":\"1111\",\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"01234567\"},\"processorInformation\":{\"merchantNumber\":\"000123456789012\",\"approvalCode\":\"831000\",\"networkTransactionId\":\"016150703802094\",\"transactionId\":\"016150703802094\",\"responseCode\":\"00\",\"avs\":{\"code\":\"Y\",\"codeRaw\":\"Y\"}},\"reconciliationId\":\"1081AVM3KYS2\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2024-05-01T21:15:35Z\"}"
PRE
end

def post_scrubbed_nt
<<-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\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"paymentInformation\":{\"tokenizedCard\":{\"number\":\"[FILTERED]\",\"expirationMonth\":9,\"expirationYear\":2025,\"cryptogram\":\"[FILTERED]\",\"type\":\"001\",\"transactionType\":\"3\"}},\"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\"}},\"processingInformation\":{\"commerceIndicator\":\"internet\",\"paymentSolution\":\"015\",\"authorizationOptions\":{}}}"
-> "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/7145981349676498704951/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/7145981349676498704951\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/captures\"}},\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"id\":\"7145981349676498704951\",\"issuerInformation\":{\"responseRaw\":\"0110322000000E10000200000000000001022105012115353420253130383141564D334B5953323833313030303030000159008000223134573031363135303730333830323039344730363400103232415050524F56414C00065649435243200034544B54523031313132313231323132313231544C3030323636504E30303431313131\"},\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"requestorId\":\"12121212121\",\"assuranceLevel\":\"66\",\"type\":\"001\"},\"card\":{\"suffix\":\"1111\",\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"01234567\"},\"processorInformation\":{\"merchantNumber\":\"000123456789012\",\"approvalCode\":\"831000\",\"networkTransactionId\":\"016150703802094\",\"transactionId\":\"016150703802094\",\"responseCode\":\"00\",\"avs\":{\"code\":\"Y\",\"codeRaw\":\"Y\"}},\"reconciliationId\":\"1081AVM3KYS2\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2024-05-01T21:15:35Z\"}"
POST
end

def successful_purchase_response
<<-RESPONSE
{
Expand Down

0 comments on commit 0faace6

Please sign in to comment.