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

Shift4: Update response parsing to account for hostresponse #5261

Merged
merged 1 commit into from
Sep 13, 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 @@ -38,6 +38,7 @@
* Stripe PI: Update Stored Credentials [almalee24] #5236
* Checkout V2: Update stored credential options function [jherreraa] #5239
* Ebanx: Add support for Stored Credentials [almalee24] #5243
* Shift4: Update response parsing to account for hostresponse [jcreiff] #5261

== Version 1.137.0 (August 2, 2024)
* Unlock dependency on `rexml` to allow fixing a CVE (#5181).
Expand Down
13 changes: 11 additions & 2 deletions lib/active_merchant/billing/gateways/shift4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,24 @@ def parse(body)
end

def message_from(action, response)
success_from(action, response) ? 'Transaction successful' : (error(response)&.dig('longText') || response['result'].first&.dig('transaction', 'hostResponse', 'reasonDescription') || 'Transaction declined')
if success_from(action, response)
'Transaction successful'
else
error(response)&.dig('longText') ||
response['result'].first&.dig('transaction', 'hostresponse', 'reasonDescription') ||
response['result'].first&.dig('transaction', 'hostResponse', 'reasonDescription') ||
'Transaction declined'
end
end

def error_code_from(action, response)
code = response['result'].first&.dig('transaction', 'responseCode')
primary_code = response['result'].first['error'].present?
return unless code == 'D' || primary_code == true || success_from(action, response)

if response['result'].first&.dig('transaction', 'hostResponse')
if response['result'].first&.dig('transaction', 'hostresponse')
response['result'].first&.dig('transaction', 'hostresponse', 'reasonCode')
elsif response['result'].first&.dig('transaction', 'hostResponse')
response['result'].first&.dig('transaction', 'hostResponse', 'reasonCode')
elsif response['result'].first['error']
response['result'].first&.dig('error', 'primaryCode')
Expand Down
4 changes: 2 additions & 2 deletions test/remote/gateways/remote_shift4_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def test_failure_on_referral_transactions
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
assert_include response.message, 'Card for Merchant Id 0008628968 not found'
assert_include response.message, 'Unable to determine card type. Please check the number and re-enter.'
end

def test_failed_authorize_with_failure_amount
Expand All @@ -214,7 +214,7 @@ def test_failed_authorize_with_error_message
def test_failed_capture
response = @gateway.capture(@amount, '', @options)
assert_failure response
assert_include response.message, 'Card for Merchant Id 0008628968 not found'
assert_include response.message, 'Unable to determine card type. Please check the number and re-enter.'
end

def test_failed_refund
Expand Down
116 changes: 116 additions & 0 deletions test/unit/gateways/shift4_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def test_failed_purchase

assert_failure response
assert_equal response.message, 'Transaction declined'
assert_equal 'D', response.error_code
assert_equal 'A', response.avs_result['code']
assert_equal 'Street address matches, but postal code does not match.', response.avs_result['message']
assert_nil response.authorization
Expand All @@ -260,6 +261,8 @@ def test_failed_authorize
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_nil response.authorization
assert_equal 'GTV Msg: ERROR{0} 20018: no default category found, UC, Mod10=N TOKEN01CE ENGINE29CE', response.message
assert_equal 9100, response.error_code
assert response.test?
end

Expand All @@ -270,6 +273,18 @@ def test_failed_authorize_with_host_response

assert_failure response
assert_equal 'CVV value N not accepted.', response.message
assert_equal 'N7', response.error_code
assert response.test?
end

def test_failed_authorize_with_alternate_host_response
response = stub_comms do
@gateway.authorize(@amount, @credit_card)
end.respond_with(failed_authorize_with_alternate_host_response)

assert_failure response
assert_equal 'Invalid Merchant', response.message
assert_equal '03', response.error_code
assert response.test?
end

Expand All @@ -290,6 +305,8 @@ def test_failed_capture
response = @gateway.capture(@amount, 'abc', @options)
assert_failure response
assert_nil response.authorization
assert_equal 'INTERNET FAILURE: Timeout waiting for response across the Internet UTGAPI05CE', response.message
assert_equal 9961, response.error_code
assert response.test?
end

Expand All @@ -309,6 +326,8 @@ def test_failed_void
response = @gateway.void('', @options)
assert_failure response
assert_nil response.authorization
assert_equal 'Invoice Not Found 00000000kl 0008628968 ENGINE29CE', response.message
assert_equal 9815, response.error_code
assert response.test?
end

Expand Down Expand Up @@ -1277,4 +1296,101 @@ def failed_authorize_with_host_response
}
RESPONSE
end

def failed_authorize_with_alternate_host_response
<<~RESPONSE
{
"result": [
{
"dateTime": "2024-09-06T12:46:05.000-07:00",
"receiptColumns": 30,
"correlationId": "A6D33AD9-29BE-44A3-B6B4-8FC6354A0514",
"amount": {
"total": 2118.37
},
"card": {
"type": "VS",
"entryMode": "M",
"number": "[FILTERED]",
"present": "N",
"token": {
"value": "657492f6d9cx5qmf"
}
},
"clerk": {
"numericId": 1
},
"customer": {
"addressLine1": "13238 N 101st Pl",
"emailAddress": "howardholleb@everonsolutions.com",
"firstName": "[FILTERED]",
"lastName": "[FILTERED]",
"postalCode": "85260"
},
"device": {
"capability": {
"magstripe": "Y",
"manualEntry": "Y"
}
},
"merchant": {
"mid": 8723645,
"name": "NEW CARDINALS STADIUM"
},
"receipt": [
{
"key": "MaskedPAN",
"printValue": "XXXXXXXXXXXX6574"
},
{
"key": "CardEntryMode",
"printName": "ENTRY METHOD",
"printValue": "KEYED"
},
{
"key": "SignatureRequired",
"printValue": "N"
},
{
"key": "TerminalID",
"printName": "TID",
"printValue": "78084447"
}
],
"server": {
"name": "UTGAPI04S7"
},
"transaction": {
"authSource": "E",
"HEY": "WHOA",
"avs": {
"postalCodeVerified": "Y",
"result": "Y",
"streetVerified": "Y",
"valid": "Y"
},
"cardOnFile": {
"indicator": "01",
"scheduledIndicator": "02",
"usageIndicator": "01"
},
"invoice": "0725417280",
"hostresponse": {
"reasonCode": "03",
"reattemptPermission": "Reattempt permitted 15 times in 30 days",
"reasonDescription": "Invalid Merchant"
},
"responseCode": "D",
"retrievalReference": "425019365998",
"saleFlag": "S",
"vendorReference": "19026022674001"
},
"universalToken": {
"value": "480709-62ADAB16-000B58-00004E9E-191C7407826"
}
}
]
}
RESPONSE
end
end
Loading