Skip to content

Commit

Permalink
Stripe and Stripe PI: add headers to response body
Browse files Browse the repository at this point in the history
  • Loading branch information
yunnydang committed Aug 1, 2024
1 parent 0c9acc1 commit d9cffb6
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* DecicirPlus: Update error_message to add safety navigator [almalee24] #5187
* Elavon: Add updated stored credential version [almalee24] #5170
* Adyen: Add header fields to response body [yunnydang] #5184
* Stripe and Stripe PI: Add header fields to response body [yunnydang] #5185

== Version 1.136.0 (June 3, 2024)
* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
Expand Down
27 changes: 26 additions & 1 deletion lib/active_merchant/billing/gateways/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,21 @@ def add_radar_data(post, options = {})
post[:radar_options] = radar_options unless radar_options.empty?
end

def add_header_fields(response)
return unless @response_headers.present?

headers = {}
headers['response_headers'] = {}
headers['response_headers']['idempotent_replayed'] = @response_headers['idempotent-replayed'] if @response_headers['idempotent-replayed']
headers['response_headers']['stripe_should_retry'] = @response_headers['stripe-should-retry'] if @response_headers['stripe-should-retry']

response.merge!(headers)
end

def parse(body)
JSON.parse(body)
response = JSON.parse(body)
add_header_fields(response)
response
end

def post_data(params)
Expand Down Expand Up @@ -752,6 +765,18 @@ def success_from(response, options)
!response.key?('error') && response['status'] != 'failed'
end

# Override the regular handle response so we can access the headers
# set header fields and values so we can add them to the response body
def handle_response(response)
@response_headers = response.each_header.to_h if response.respond_to?(:header)
case response.code.to_i
when 200...300
response.body
else
raise ResponseError.new(response)
end
end

def response_error(raw_response)
parse(raw_response)
rescue JSON::ParserError
Expand Down
11 changes: 11 additions & 0 deletions test/remote/gateways/remote_stripe_payment_intents_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,17 @@ def test_unsuccessful_purchase
refute purchase.params.dig('error', 'payment_intent', 'charges', 'data')[0]['captured']
end

def test_unsuccessful_purchase_returns_header_response
options = {
currency: 'GBP',
customer: @customer
}
assert purchase = @gateway.purchase(@amount, @declined_payment_method, options)

assert_equal 'Your card was declined.', purchase.message
assert_not_nil purchase.params['response_headers']['stripe_should_retry']
end

def test_successful_purchase_with_external_auth_data_3ds_1
options = {
currency: 'GBP',
Expand Down
8 changes: 8 additions & 0 deletions test/remote/gateways/remote_stripe_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ def test_unsuccessful_purchase
assert_match(/ch_[a-zA-Z\d]+/, response.authorization)
end

def test_unsuccessful_purchase_returns_response_headers
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_match %r{Your card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
assert_not_nil response.params['response_headers']['stripe_should_retry']
end

def test_unsuccessful_purchase_with_destination_and_amount
destination = fixtures(:stripe_destination)[:stripe_user_id]
custom_options = @options.merge(destination: destination, destination_amount: @amount + 20)
Expand Down
9 changes: 9 additions & 0 deletions test/unit/gateways/stripe_payment_intents_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,15 @@ def test_successful_purchase
end.respond_with(successful_create_intent_response)
end

def test_failed_authorize_with_idempotent_replayed
@gateway.instance_variable_set(:@response_headers, { 'idempotent-replayed' => 'true' })
@gateway.expects(:ssl_request).returns(failed_payment_method_response)

response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert response.params['response_headers']['idempotent_replayed'], 'true'
end

def test_failed_error_on_requires_action
@gateway.expects(:ssl_request).returns(failed_with_set_error_on_requires_action_response)

Expand Down
13 changes: 13 additions & 0 deletions test/unit/gateways/stripe_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,19 @@ def test_declined_request
assert_equal 'ch_test_charge', response.authorization
end

def test_declined_request_returns_header_response
@gateway.instance_variable_set(:@response_headers, { 'idempotent-replayed' => 'true' })
@gateway.expects(:ssl_request).returns(declined_purchase_response)

assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response

assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
refute response.test? # unsuccessful request defaults to live
assert_equal 'ch_test_charge', response.authorization
assert response.params['response_headers']['idempotent_replayed'], 'true'
end

def test_declined_request_advanced_decline_codes
@gateway.expects(:ssl_request).returns(declined_call_issuer_purchase_response)

Expand Down

0 comments on commit d9cffb6

Please sign in to comment.