diff --git a/lib/mpp/methods/stripe/charge_intent.rb b/lib/mpp/methods/stripe/charge_intent.rb index 1b0bc0d..30136b2 100644 --- a/lib/mpp/methods/stripe/charge_intent.rb +++ b/lib/mpp/methods/stripe/charge_intent.rb @@ -60,7 +60,10 @@ def verify(credential, request) begin client = ::Stripe::StripeClient.new(@secret_key) - result = client.v1.payment_intents.create(params) + result = client.v1.payment_intents.create( + params, + {idempotency_key: stripe_idempotency_key(credential)} + ) rescue => e raise Mpp::VerificationError, e.message end @@ -84,6 +87,12 @@ def verify(credential, request) Mpp::Receipt.success(pi_id, method: "stripe", external_id: external_id) end + + private + + def stripe_idempotency_key(credential) + "mpp-stripe-charge-#{credential.challenge.id}" + end end end end diff --git a/test/mpp/methods/stripe/test_charge_intent.rb b/test/mpp/methods/stripe/test_charge_intent.rb index 370f05c..25c76fe 100644 --- a/test/mpp/methods/stripe/test_charge_intent.rb +++ b/test/mpp/methods/stripe/test_charge_intent.rb @@ -75,7 +75,11 @@ def test_verify_calls_stripe_sdk mock_result = Struct.new(:id, :status).new("pi_abc123", "succeeded") mock_pi = Minitest::Mock.new - mock_pi.expect(:create, mock_result, [Hash]) + mock_pi.expect(:create, mock_result) do |params, opts| + assert_equal 100, params[:amount] + assert_equal "usd", params[:currency] + assert_equal "mpp-stripe-charge-test-id", opts[:idempotency_key] + end mock_v1 = Struct.new(:payment_intents).new(mock_pi) mock_client = Struct.new(:v1).new(mock_v1) @@ -123,7 +127,7 @@ def test_verify_rejects_replayed_payment mock_last_response = Struct.new(:headers).new(mock_headers) mock_result = Struct.new(:id, :status, :last_response).new("pi_abc123", "succeeded", mock_last_response) mock_pi = Minitest::Mock.new - mock_pi.expect(:create, mock_result, [Hash]) + mock_pi.expect(:create, mock_result, [Hash, {idempotency_key: "mpp-stripe-charge-test-id"}]) mock_v1 = Struct.new(:payment_intents).new(mock_pi) mock_client = Struct.new(:v1).new(mock_v1) @@ -144,7 +148,7 @@ def test_verify_rejects_requires_action mock_result = Struct.new(:id, :status).new("pi_needs3ds", "requires_action") mock_pi = Minitest::Mock.new - mock_pi.expect(:create, mock_result, [Hash]) + mock_pi.expect(:create, mock_result, [Hash, {idempotency_key: "mpp-stripe-charge-test-id"}]) mock_v1 = Struct.new(:payment_intents).new(mock_pi) mock_client = Struct.new(:v1).new(mock_v1)