Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HPs: Update NetworkTokenizationCreditCard flow #5178

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* CommerceHub: Update test url [DustinHaefele] #5211
* Elavon: Update sending CVV for MIT transactions [almalee24] #5210
* Adyen: Fix NT integration [jherreraa] #5155
* HPS: Update NetworkTokenizationCreditCard flow [almalee24] #5178

== Version 1.137.0 (August 2, 2024)
* Unlock dependency on `rexml` to allow fixing a CVE (#5181).
Expand Down
71 changes: 31 additions & 40 deletions lib/active_merchant/billing/gateways/hps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ class HpsGateway < Gateway

PAYMENT_DATA_SOURCE_MAPPING = {
apple_pay: 'ApplePay',
master: 'MasterCard 3DSecure',
visa: 'Visa 3DSecure',
american_express: 'AMEX 3DSecure',
discover: 'Discover 3DSecure',
android_pay: 'GooglePayApp',
google_pay: 'GooglePayApp'
}

Expand All @@ -30,15 +25,16 @@ def initialize(options = {})
super
end

def authorize(money, card_or_token, options = {})
def authorize(money, payment_method, options = {})
commit('CreditAuth') do |xml|
add_amount(xml, money)
add_allow_dup(xml)
add_card_or_token_customer_data(xml, card_or_token, options)
add_card_or_token_customer_data(xml, payment_method, options)
add_details(xml, options)
add_descriptor_name(xml, options)
add_card_or_token_payment(xml, card_or_token, options)
add_three_d_secure(xml, card_or_token, options)
add_card_or_token_payment(xml, payment_method, options)
add_wallet_data(xml, payment_method, options)
add_three_d_secure(xml, payment_method, options)
add_stored_credentials(xml, options)
end
end
Expand Down Expand Up @@ -110,7 +106,8 @@ def scrub(transcript)
gsub(%r((<hps:SecretAPIKey>)[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2').
gsub(%r((<hps:PaymentData>)[^<]*(<\/hps:PaymentData>))i, '\1[FILTERED]\2').
gsub(%r((<hps:RoutingNumber>)[^<]*(<\/hps:RoutingNumber>))i, '\1[FILTERED]\2').
gsub(%r((<hps:AccountNumber>)[^<]*(<\/hps:AccountNumber>))i, '\1[FILTERED]\2')
gsub(%r((<hps:AccountNumber>)[^<]*(<\/hps:AccountNumber>))i, '\1[FILTERED]\2').
gsub(%r((<hps:Cryptogram>)[^<]*(<\/hps:Cryptogram>))i, '\1[FILTERED]\2')
end

private
Expand All @@ -125,28 +122,30 @@ def commit_check_sale(money, check, options)
end
end

def commit_credit_sale(money, card_or_token, options)
def commit_credit_sale(money, payment_method, options)
commit('CreditSale') do |xml|
add_amount(xml, money)
add_allow_dup(xml)
add_card_or_token_customer_data(xml, card_or_token, options)
add_card_or_token_customer_data(xml, payment_method, options)
add_details(xml, options)
add_descriptor_name(xml, options)
add_card_or_token_payment(xml, card_or_token, options)
add_three_d_secure(xml, card_or_token, options)
add_card_or_token_payment(xml, payment_method, options)
add_wallet_data(xml, payment_method, options)
add_three_d_secure(xml, payment_method, options)
add_stored_credentials(xml, options)
end
end

def commit_recurring_billing_sale(money, card_or_token, options)
def commit_recurring_billing_sale(money, payment_method, options)
commit('RecurringBilling') do |xml|
add_amount(xml, money)
add_allow_dup(xml)
add_card_or_token_customer_data(xml, card_or_token, options)
add_card_or_token_customer_data(xml, payment_method, options)
add_details(xml, options)
add_descriptor_name(xml, options)
add_card_or_token_payment(xml, card_or_token, options)
add_three_d_secure(xml, card_or_token, options)
add_card_or_token_payment(xml, payment_method, options)
add_wallet_data(xml, payment_method, options)
add_three_d_secure(xml, payment_method, options)
add_stored_credentials(xml, options)
add_stored_credentials_for_recurring_billing(xml, options)
end
Expand Down Expand Up @@ -254,32 +253,24 @@ def add_descriptor_name(xml, options)
xml.hps :TxnDescriptor, options[:descriptor_name] if options[:descriptor_name]
end

def add_three_d_secure(xml, card_or_token, options)
if card_or_token.is_a?(NetworkTokenizationCreditCard)
build_three_d_secure(xml, {
source: card_or_token.source,
cavv: card_or_token.payment_cryptogram,
eci: card_or_token.eci,
xid: card_or_token.transaction_id
})
elsif options[:three_d_secure]
options[:three_d_secure][:source] ||= card_brand(card_or_token)
build_three_d_secure(xml, options[:three_d_secure])
def add_wallet_data(xml, payment_method, options)
return unless payment_method.is_a?(NetworkTokenizationCreditCard)

xml.hps :WalletData do
xml.hps :PaymentSource, PAYMENT_DATA_SOURCE_MAPPING[payment_method.source]
xml.hps :Cryptogram, payment_method.payment_cryptogram
xml.hps :ECI, strip_leading_zero(payment_method.eci) if payment_method.eci
end
end

def build_three_d_secure(xml, three_d_secure)
# PaymentDataSource is required when supplying the SecureECommerce data group,
# and the gateway currently only allows the values within the mapping
return unless PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
def add_three_d_secure(xml, card_or_token, options)
return unless (three_d_secure = options[:three_d_secure])

xml.hps :SecureECommerce do
xml.hps :PaymentDataSource, PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
xml.hps :TypeOfPaymentData, '3DSecure' # Only type currently supported
xml.hps :PaymentData, three_d_secure[:cavv] if three_d_secure[:cavv]
# the gateway only allows a single character for the ECI
xml.hps :ECommerceIndicator, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci]
xml.hps :XID, three_d_secure[:xid] if three_d_secure[:xid]
xml.hps :Secure3D do
xml.hps :Version, three_d_secure[:version]
xml.hps :AuthenticationValue, three_d_secure[:cavv] if three_d_secure[:cavv]
xml.hps :ECI, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci]
xml.hps :DirectoryServerTxnId, three_d_secure[:ds_transaction_id] if three_d_secure[:ds_transaction_id]
end
end

Expand Down
51 changes: 0 additions & 51 deletions test/remote/gateways/remote_hps_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@ def test_transcript_scrubbing_with_cryptogram
credit_card = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
source: :apple_pay
)
Expand Down Expand Up @@ -435,56 +434,6 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
assert_equal 'Success', response.message
end

def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
credit_card = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
source: :android_pay
)
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end

def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
credit_card = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
source: :android_pay
)
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end

def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
credit_card = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
source: :android_pay
)
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end

def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
credit_card = network_tokenization_credit_card(
'4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
source: :android_pay
)
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end

def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
credit_card = network_tokenization_credit_card(
'4242424242424242',
Expand Down
52 changes: 24 additions & 28 deletions test/unit/gateways/hps_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -639,21 +639,21 @@ def test_three_d_secure_visa

options = {
three_d_secure: {
version: '2.2.0',
cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '05',
xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
ds_transaction_id: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
}
}

response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data)
assert_match(/<hps:PaymentDataSource>Visa 3DSecure<\/hps:PaymentDataSource>/, data)
assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data)
assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data)
assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data)
assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data)
assert_match(/<hps:Secure3D>(.*)<\/hps:Secure3D>/, data)
assert_match(/<hps:Version>#{options[:three_d_secure][:version]}<\/hps:Version>/, data)
assert_match(/<hps:AuthenticationValue>#{options[:three_d_secure][:cavv]}<\/hps:AuthenticationValue>/, data)
assert_match(/<hps:ECI>5<\/hps:ECI>/, data)
assert_match(/<hps:DirectoryServerTxnId>#{options[:three_d_secure][:ds_transaction_id]}<\/hps:DirectoryServerTxnId>/, data)
end.respond_with(successful_charge_response)

assert_success response
Expand All @@ -666,21 +666,21 @@ def test_three_d_secure_mastercard

options = {
three_d_secure: {
version: '2.2.0',
cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '05',
xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
ds_transaction_id: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
}
}

response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data)
assert_match(/<hps:PaymentDataSource>MasterCard 3DSecure<\/hps:PaymentDataSource>/, data)
assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data)
assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data)
assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data)
assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data)
assert_match(/<hps:Secure3D>(.*)<\/hps:Secure3D>/, data)
assert_match(/<hps:Version>#{options[:three_d_secure][:version]}<\/hps:Version>/, data)
assert_match(/<hps:AuthenticationValue>#{options[:three_d_secure][:cavv]}<\/hps:AuthenticationValue>/, data)
assert_match(/<hps:ECI>5<\/hps:ECI>/, data)
assert_match(/<hps:DirectoryServerTxnId>#{options[:three_d_secure][:ds_transaction_id]}<\/hps:DirectoryServerTxnId>/, data)
end.respond_with(successful_charge_response)

assert_success response
Expand All @@ -695,19 +695,17 @@ def test_three_d_secure_discover
three_d_secure: {
cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '5',
xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
ds_transaction_id: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
}
}

response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data)
assert_match(/<hps:PaymentDataSource>Discover 3DSecure<\/hps:PaymentDataSource>/, data)
assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data)
assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data)
assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data)
assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data)
assert_match(/<hps:Secure3D>(.*)<\/hps:Secure3D>/, data)
assert_match(/<hps:AuthenticationValue>#{options[:three_d_secure][:cavv]}<\/hps:AuthenticationValue>/, data)
assert_match(/<hps:ECI>5<\/hps:ECI>/, data)
assert_match(/<hps:DirectoryServerTxnId>#{options[:three_d_secure][:ds_transaction_id]}<\/hps:DirectoryServerTxnId>/, data)
end.respond_with(successful_charge_response)

assert_success response
Expand All @@ -722,19 +720,17 @@ def test_three_d_secure_amex
three_d_secure: {
cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '05',
xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
ds_transaction_id: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
}
}

response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data)
assert_match(/<hps:PaymentDataSource>AMEX 3DSecure<\/hps:PaymentDataSource>/, data)
assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data)
assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data)
assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data)
assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data)
assert_match(/<hps:Secure3D>(.*)<\/hps:Secure3D>/, data)
assert_match(/<hps:AuthenticationValue>#{options[:three_d_secure][:cavv]}<\/hps:AuthenticationValue>/, data)
assert_match(/<hps:ECI>5<\/hps:ECI>/, data)
assert_match(/<hps:DirectoryServerTxnId>#{options[:three_d_secure][:ds_transaction_id]}<\/hps:DirectoryServerTxnId>/, data)
end.respond_with(successful_charge_response)

assert_success response
Expand Down
Loading