Skip to content

Commit

Permalink
CYBS: Recurring NT
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aenand committed Aug 22, 2023
1 parent 7172325 commit ee2ac1a
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 13 additions & 5 deletions lib/active_merchant/billing/gateways/cyber_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions test/remote/gateways/remote_cyber_source_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}" }

Expand Down
47 changes: 47 additions & 0 deletions test/unit/gateways/cyber_source_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'<cavv>', request_body
assert_not_match %r'<xid>', request_body
assert_match %r'<commerceIndicator>internet</commerceIndicator>', 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'<authenticationData>', request_body
assert_match %r'<commerceIndicator>internet</commerceIndicator>', 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)

Expand Down

0 comments on commit ee2ac1a

Please sign in to comment.