From 6b55843a5f306e49b26dbdc1b0fc9b7fc59341b1 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Tue, 19 Sep 2023 11:56:16 +0200 Subject: [PATCH 1/7] [stripe] added parsing response into Response and tests --- lib/gringotts/gateways/stripe.ex | 66 ++++++++++++++++++----- test/integration/gateways/stripe_test.exs | 48 ++++++++++++++--- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index 2923c4b3..d0df2343 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -80,7 +80,7 @@ defmodule Gringotts.Gateways.Stripe do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:secret_key] - alias Gringotts.{Address, CreditCard, Money} + alias Gringotts.{Address, CreditCard, Money, Response} @doc """ Performs a (pre) Authorize operation. @@ -223,10 +223,10 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.void(Gringotts.Gateways.Stripe, id, opts) """ - @spec void(String.t(), keyword) :: map - def void(id, opts) do + @spec void(keyword) :: map + def void(opts) do params = optional_params(opts) - commit(:post, "charges/#{id}/refund", params, opts) + commit(:post, "refunds", params, opts) end @doc """ @@ -245,10 +245,10 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.refund(Gringotts.Gateways.Stripe, amount, id, opts) """ - @spec refund(Money.t(), String.t(), keyword) :: map - def refund(amount, id, opts) do + @spec refund(Money.t(), keyword) :: map + def refund(amount, opts) do params = optional_params(opts) ++ amount_params(amount) - commit(:post, "charges/#{id}/refund", params, opts) + commit(:post, "refunds", params, opts) end @doc """ @@ -381,7 +381,7 @@ defmodule Gringotts.Gateways.Stripe do ] response = HTTPoison.request(method, "#{@base_url}/#{path}", {:form, params}, headers) - format_response(response) + respond(response) end defp optional_params(opts) do @@ -390,10 +390,52 @@ defmodule Gringotts.Gateways.Stripe do |> Keyword.delete(:address) end - defp format_response(response) do - case response do - {:ok, %HTTPoison.Response{body: body}} -> body |> Jason.decode!() - _ -> %{"error" => "something went wrong, please try again later"} + # Parses STRIPE's response and returns a `Gringotts.Response` struct in a + # `:ok`, `:error` tuple. + @spec respond(term) :: {:ok | :error, Response.t()} + defp respond(stripe_response) + + defp respond({:ok, %{status_code: 200, body: body}}) do + common = [raw: body, status_code: 200, gateway_code: 200] + + with {:ok, decoded_json} <- Jason.decode(body), + {:ok, results} <- parse_response(decoded_json) do + {:ok, Response.success(common ++ results)} + else + {:not_ok, errors} -> + {:ok, Response.error(common ++ errors)} + + {:error, _} -> + {:error, Response.error([reason: "undefined response from stripe"] ++ common)} end end + + defp respond({:ok, %{status_code: status_code, body: body}}) do + {:error, Response.error(status_code: status_code, raw: body)} + end + + defp respond({:error, %HTTPoison.Error{} = error}) do + { + :error, + Response.error( + reason: "network related failure", + message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]" + ) + } + end + + defp parse_response(data) do + address = struct(%Address{}, data["billing_details"]["address"]) + + results = [ + id: data["id"], + token: data["balance_transaction"], + message: data["description"], + fraud_review: data["outcome"]["risk_level"], + cvc_result: data["payment_method_details"]["card"]["checks"]["cvc_check"], + avs_result: %{address: address} + ] + + {:ok, results} + end end diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index 60dbefc5..bb95c2a8 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -1,10 +1,10 @@ defmodule Gringotts.Gateways.StripeTest do use ExUnit.Case - alias Gringotts.{Address, CreditCard} + alias Gringotts.{Address, CreditCard, Response} alias Gringotts.Gateways.Stripe - @moduletag integration: true + # @moduletag integration: true @amount Gringotts.FakeMoney.new(5, :USD) @card %CreditCard{ @@ -16,6 +16,7 @@ defmodule Gringotts.Gateways.StripeTest do month: "12", verification_code: "123" } + @card_token "tok_visa" @address %Address{ street1: "123 Main", @@ -28,14 +29,47 @@ defmodule Gringotts.Gateways.StripeTest do @required_opts [config: [secret_key: "sk_test_vIX41hayC0BKrPWQerLuOMld"]] @optional_opts [address: @address] + @present_opts [description: "best purchase ever", receipt_email: "something@example.com"] describe "authorize/3" do test "with correct params" do - response = Stripe.authorize(@amount, @card, @required_opts ++ @optional_opts) - assert Map.has_key?(response, "id") - assert response["amount"] == 500 - assert response["captured"] == false - assert response["currency"] == "usd" + opts = @required_opts ++ @optional_opts + {:ok, %Response{} = response} = Stripe.authorize(@amount, @card_token, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "ch_") + assert String.contains?(response.raw, "\"amount\": 500") + assert String.contains?(response.raw, "\"amount_captured\": 0") + assert String.contains?(response.raw, "\"captured\": false") + assert String.contains?(response.raw, "\"currency\": \"usd\"") + end + end + + describe "purchase/3" do + test "with correct params" do + opts = @required_opts ++ @optional_opts + {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_token, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "ch_") + assert response.message == nil + assert response.fraud_review == "normal" + assert String.contains?(response.raw, "\"amount\": 500") + assert String.contains?(response.raw, "\"amount_captured\": 500") + assert String.contains?(response.raw, "\"captured\": true") + assert String.contains?(response.raw, "\"currency\": \"usd\"") + end + + test "with additional options" do + opts = @required_opts ++ @optional_opts ++ @present_opts + {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_token, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "ch_") + assert response.message == "best purchase ever" + assert response.fraud_review == "normal" + assert String.contains?(response.raw, "\"amount\": 500") + assert String.contains?(response.raw, "\"amount_captured\": 500") + assert String.contains?(response.raw, "\"captured\": true") + assert String.contains?(response.raw, "\"currency\": \"usd\"") + assert String.contains?(response.raw, "\"receipt_email\": \"something@example.com\"") end end end From 68d9620c7ff14097bc3465e8b8410a22010ce804 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Tue, 19 Sep 2023 14:51:09 +0200 Subject: [PATCH 2/7] [stripe] added refund specific response --- lib/gringotts/gateways/stripe.ex | 73 +++++++++++++---------- test/integration/gateways/stripe_test.exs | 14 +++++ 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index d0df2343..52d6e31d 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -125,10 +125,11 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.authorize(Gringotts.Gateways.Stripe, amount, card, opts) """ - @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: map + @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response.t()} def authorize(amount, payment, opts) do params = create_params_for_auth_or_purchase(amount, payment, opts, false) - commit(:post, "charges", params, opts) + response = commit(:post, "charges", params, opts) + respond(response, :purchase) end @doc """ @@ -163,10 +164,11 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.purchase(Gringotts.Gateways.Stripe, amount, card, opts) """ - @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: map + @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response.t()} def purchase(amount, payment, opts) do params = create_params_for_auth_or_purchase(amount, payment, opts) - commit(:post, "charges", params, opts) + response = commit(:post, "charges", params, opts) + respond(response, :purchase) end @doc """ @@ -190,10 +192,11 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.capture(Gringotts.Gateways.Stripe, id, amount, opts) """ - @spec capture(String.t(), Money.t(), keyword) :: map + @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response.t()} def capture(id, amount, opts) do params = optional_params(opts) ++ amount_params(amount) - commit(:post, "charges/#{id}/capture", params, opts) + response = commit(:post, "charges/#{id}/capture", params, opts) + respond(response, :purchase) end @doc """ @@ -223,10 +226,9 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.void(Gringotts.Gateways.Stripe, id, opts) """ - @spec void(keyword) :: map - def void(opts) do - params = optional_params(opts) - commit(:post, "refunds", params, opts) + @spec void(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()} + def void(amount, charge_id, opts \\ []) do + refund(amount, charge_id, opts) end @doc """ @@ -245,10 +247,14 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.refund(Gringotts.Gateways.Stripe, amount, id, opts) """ - @spec refund(Money.t(), keyword) :: map - def refund(amount, opts) do - params = optional_params(opts) ++ amount_params(amount) - commit(:post, "refunds", params, opts) + @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()} + def refund(amount, charge_id, opts \\ []) do + params = + [charge: charge_id] ++ + Keyword.take(amount_params(amount), [:amount]) + + response = commit(:post, "refunds", params, opts) + respond(response, :refund) end @doc """ @@ -283,7 +289,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.store(Gringotts.Gateways.Stripe, card, opts) """ - @spec store(CreditCard.t() | String.t(), keyword) :: map + @spec store(CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response.t()} def store(payment, opts) do params = optional_params(opts) ++ source_params(payment, opts) commit(:post, "customers", params, opts) @@ -303,7 +309,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.unstore(Gringotts.Gateways.Stripe, id, opts) """ - @spec unstore(String.t(), keyword) :: map + @spec unstore(String.t(), keyword) :: {:ok | :error, Response.t()} def unstore(id, opts), do: commit(:delete, "customers/#{id}", [], opts) # Private methods @@ -334,14 +340,10 @@ defmodule Gringotts.Gateways.Stripe do defp source_params(%CreditCard{} = card, opts) do params = card_params(card) ++ address_params(opts[:address]) - response = create_card_token(params, opts) - - if Map.has_key?(response, "error") do - [] + with {:ok, response} <- create_card_token(params, opts) do + source_params(response.id, opts) else - response - |> Map.get("id") - |> source_params(opts) + _ -> [] end end @@ -381,7 +383,6 @@ defmodule Gringotts.Gateways.Stripe do ] response = HTTPoison.request(method, "#{@base_url}/#{path}", {:form, params}, headers) - respond(response) end defp optional_params(opts) do @@ -392,14 +393,14 @@ defmodule Gringotts.Gateways.Stripe do # Parses STRIPE's response and returns a `Gringotts.Response` struct in a # `:ok`, `:error` tuple. - @spec respond(term) :: {:ok | :error, Response.t()} - defp respond(stripe_response) + @spec respond(term, Atom.t()) :: {:ok | :error, Response.t()} + defp respond(stripe_response, gringotts_action) - defp respond({:ok, %{status_code: 200, body: body}}) do + defp respond({:ok, %{status_code: 200, body: body}}, gringotts_action) do common = [raw: body, status_code: 200, gateway_code: 200] with {:ok, decoded_json} <- Jason.decode(body), - {:ok, results} <- parse_response(decoded_json) do + {:ok, results} <- parse_response(decoded_json, gringotts_action) do {:ok, Response.success(common ++ results)} else {:not_ok, errors} -> @@ -410,11 +411,11 @@ defmodule Gringotts.Gateways.Stripe do end end - defp respond({:ok, %{status_code: status_code, body: body}}) do + defp respond({:ok, %{status_code: status_code, body: body}}, _) do {:error, Response.error(status_code: status_code, raw: body)} end - defp respond({:error, %HTTPoison.Error{} = error}) do + defp respond({:error, %HTTPoison.Error{} = error}, _) do { :error, Response.error( @@ -424,7 +425,7 @@ defmodule Gringotts.Gateways.Stripe do } end - defp parse_response(data) do + defp parse_response(data, gringotts_action) when gringotts_action == :purchase do address = struct(%Address{}, data["billing_details"]["address"]) results = [ @@ -438,4 +439,14 @@ defmodule Gringotts.Gateways.Stripe do {:ok, results} end + + defp parse_response(data, gringotts_action) when gringotts_action == :refund do + results = [ + id: data["id"], + token: data["balance_transaction"], + message: data["reason"] + ] + + {:ok, results} + end end diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index bb95c2a8..b942fbb0 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -72,4 +72,18 @@ defmodule Gringotts.Gateways.StripeTest do assert String.contains?(response.raw, "\"receipt_email\": \"something@example.com\"") end end + + describe "refund/3" do + test "with correct params" do + opts = @required_opts ++ @optional_opts + {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_token, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "ch_") + {:ok, %Response{} = refund_response} = Stripe.refund(@amount, response.id, @required_opts) + assert not is_nil(refund_response.id) + assert String.starts_with?(refund_response.id, "re_") + assert String.contains?(refund_response.raw, "\"amount\": 500") + assert String.contains?(refund_response.raw, "\"charge\": \"#{response.id}\"") + end + end end From a6e7765f213ec14536fecfdbc28c6a51a31f7fc6 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Wed, 18 Oct 2023 12:49:14 +0200 Subject: [PATCH 3/7] [] minimal implementation with payment intent and confirmation --- lib/gringotts/gateways/stripe.ex | 107 ++++++++++++++++++---- lib/gringotts/response.ex | 7 +- test/integration/gateways/stripe_test.exs | 34 ++++++- 3 files changed, 125 insertions(+), 23 deletions(-) diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index 52d6e31d..a088d097 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -127,7 +127,7 @@ defmodule Gringotts.Gateways.Stripe do """ @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response.t()} def authorize(amount, payment, opts) do - params = create_params_for_auth_or_purchase(amount, payment, opts, false) + params = create_params_for(amount, payment, opts, :authorize) response = commit(:post, "charges", params, opts) respond(response, :purchase) end @@ -166,9 +166,25 @@ defmodule Gringotts.Gateways.Stripe do """ @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response.t()} def purchase(amount, payment, opts) do - params = create_params_for_auth_or_purchase(amount, payment, opts) - response = commit(:post, "charges", params, opts) - respond(response, :purchase) + params = create_params_for(amount, payment, opts, :purchase) + + cond do + is_struct(payment) && payment.__struct__ == CreditCard -> + response = commit(:post, "payment_methods", params, opts) + respond(response, :payment_method) + + String.starts_with?(payment, "pm") -> + response = commit(:post, "payment_intents", params, opts) + respond(response, :payment_intent) + + String.starts_with?(payment, "pi") -> + response = commit(:post, "payment_intents/#{payment}/confirm", [], opts) + maybe_get_latest_charge(response, opts) + + String.starts_with?(payment, "tok") -> + response = commit(:post, "charges", params, opts) + respond(response, :purchase) + end end @doc """ @@ -314,13 +330,33 @@ defmodule Gringotts.Gateways.Stripe do # Private methods - defp create_params_for_auth_or_purchase(amount, payment, opts, capture \\ true) do - [capture: capture] ++ + defp create_params_for( + amount, + payment, + opts, + action + ) do + variable_params = + cond do + action == :authorize -> + [capture: false] + + action == :purchase && String.starts_with?(payment, "tok") -> + [capture: true] + + String.starts_with?(payment, "pm") -> + [confirm: true, confirmation_method: "manual"] + + true -> + [] + end + + variable_params ++ optional_params(opts) ++ amount_params(amount) ++ source_params(payment, opts) end defp create_card_token(params, opts) do - commit(:post, "tokens", params, opts) + commit(:post, "payment_methods", params, opts) end defp amount_params(amount) do @@ -329,9 +365,11 @@ defmodule Gringotts.Gateways.Stripe do end defp source_params(token_or_customer, _) when is_binary(token_or_customer) do - [head, _] = String.split(token_or_customer, "_") + [head | _] = String.split(token_or_customer, "_") case head do + "pi" -> [] + "pm" -> [payment_method: token_or_customer] "tok" -> [source: token_or_customer] "cus" -> [customer: token_or_customer] end @@ -351,7 +389,7 @@ defmodule Gringotts.Gateways.Stripe do defp card_params(%CreditCard{} = card) do [ - "card[name]": CreditCard.full_name(card), + type: "card", "card[number]": card.number, "card[exp_year]": card.year, "card[exp_month]": card.month, @@ -359,17 +397,16 @@ defmodule Gringotts.Gateways.Stripe do ] end - defp card_params(_), do: [] - defp address_params(%Address{} = address) do - [ - "card[address_line1]": address.street1, - "card[address_line2]": address.street2, - "card[address_city]": address.city, - "card[address_state]": address.region, - "card[address_zip]": address.postal_code, - "card[address_country]": address.country - ] + [] + # [ + # "card[address_line1]": address.street1, + # "card[address_line2]": address.street2, + # "card[address_city]": address.city, + # "card[address_state]": address.region, + # "card[address_zip]": address.postal_code, + # "card[address_country]": address.country + # ] end defp address_params(_), do: [] @@ -382,7 +419,7 @@ defmodule Gringotts.Gateways.Stripe do {"Authorization", auth_token} ] - response = HTTPoison.request(method, "#{@base_url}/#{path}", {:form, params}, headers) + HTTPoison.request(method, "#{@base_url}/#{path}", {:form, params}, headers) end defp optional_params(opts) do @@ -440,6 +477,20 @@ defmodule Gringotts.Gateways.Stripe do {:ok, results} end + defp parse_response(data, gringotts_action) when gringotts_action == :payment_intent do + results = [ + id: data["id"], + token: data["client_secret"], + message: data["latest_charge"], + reason: data["status"], + fraud_review: nil, + cvc_result: nil, + avs_result: nil + ] + + {:ok, results} + end + defp parse_response(data, gringotts_action) when gringotts_action == :refund do results = [ id: data["id"], @@ -449,4 +500,20 @@ defmodule Gringotts.Gateways.Stripe do {:ok, results} end + + defp maybe_get_latest_charge(response, opts) do + with {:ok, _} <- response, + {:ok, parsed_response} <- respond(response, :payment_intent) do + charge = parsed_response.message + + if is_nil(charge) do + {:ok, parsed_response} + else + response = commit(:get, "charges/#{charge}", [], opts) + respond(response, :purchase) + end + else + _ -> respond(response, :payment_intent) + end + end end diff --git a/lib/gringotts/response.ex b/lib/gringotts/response.ex index c64ec0a2..c71f2886 100644 --- a/lib/gringotts/response.ex +++ b/lib/gringotts/response.ex @@ -18,7 +18,8 @@ defmodule Gringotts.Response do :avs_result, :cvc_result, :raw, - :fraud_review + :fraud_review, + :status ] @typedoc """ @@ -44,6 +45,7 @@ defmodule Gringotts.Response do | `raw` | `String.t` | Raw response from the gateway. | | `fraud_review` | `term` | Gateway's risk assessment of the\ transaction. | + | `status` | `String.t` | Status of the performed action. | ## Notes @@ -74,7 +76,8 @@ defmodule Gringotts.Response do avs_result: %{street: String.t(), zip_code: String.t()}, cvc_result: String.t(), raw: String.t(), - fraud_review: term + fraud_review: term, + status: String.t() } def success(opts \\ []) do diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index b942fbb0..62d59ad5 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -16,7 +16,9 @@ defmodule Gringotts.Gateways.StripeTest do month: "12", verification_code: "123" } + @card_token "tok_visa" + @card_payent_method_3d "pm_card_authenticationRequiredOnSetup" @address %Address{ street1: "123 Main", @@ -29,7 +31,11 @@ defmodule Gringotts.Gateways.StripeTest do @required_opts [config: [secret_key: "sk_test_vIX41hayC0BKrPWQerLuOMld"]] @optional_opts [address: @address] - @present_opts [description: "best purchase ever", receipt_email: "something@example.com"] + @present_opts [ + description: "best purchase ever", + receipt_email: "something@example.com", + # return_url: "http://localhost:5000/api/3ds2" + ] describe "authorize/3" do test "with correct params" do @@ -44,6 +50,32 @@ defmodule Gringotts.Gateways.StripeTest do end end + describe "3D Secure purchase/3" do + test "it creates a payment intent and waits for card auth" do + opts = @required_opts ++ @optional_opts ++ @present_opts + {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_payent_method_3d, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "pi_") + assert response.message == "best purchase ever" + assert response.reason == "requires_source_action" + assert response.fraud_review == nil + assert String.contains?(response.raw, "\"amount\": 500") + assert String.contains?(response.raw, "\"amount_capturable\": 0") + assert String.contains?(response.raw, "\"confirmation_method\": \"manual\"") + assert String.contains?(response.raw, "\"currency\": \"usd\"") + end + + test "without card auth does not confirm the payment intent" do + opts = @required_opts ++ @optional_opts ++ @present_opts + {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_payent_method_3d, opts) + assert not is_nil(response.id) + assert String.starts_with?(response.id, "pi_") + {:ok, %Response{} = confirm_response} = Stripe.purchase(@amount, response.id, opts) + assert confirm_response.reason == "requires_source_action" + assert confirm_response.message == nil + end + end + describe "purchase/3" do test "with correct params" do opts = @required_opts ++ @optional_opts From 09c639381cf4a90ea940184c3ec93812de062c19 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Wed, 18 Oct 2023 12:51:20 +0200 Subject: [PATCH 4/7] [] message holds latest_charge --- test/integration/gateways/stripe_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index 62d59ad5..edca7afd 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -56,7 +56,7 @@ defmodule Gringotts.Gateways.StripeTest do {:ok, %Response{} = response} = Stripe.purchase(@amount, @card_payent_method_3d, opts) assert not is_nil(response.id) assert String.starts_with?(response.id, "pi_") - assert response.message == "best purchase ever" + assert response.message == nil assert response.reason == "requires_source_action" assert response.fraud_review == nil assert String.contains?(response.raw, "\"amount\": 500") From fb9bbe82488483d70e603101b1b18e704daaba85 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Wed, 18 Oct 2023 22:00:22 +0200 Subject: [PATCH 5/7] [] added return url for 3D secure --- lib/gringotts/gateways/stripe.ex | 10 +++++++++- lib/gringotts/response.ex | 13 +++++++++---- test/integration/gateways/stripe_test.exs | 4 +++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index a088d097..aba65629 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -31,6 +31,9 @@ defmodule Gringotts.Gateways.Stripe do | `statement_descriptor` | **Implemented** | | `charge` | **Implemented** | | `reason` | **Implemented** | + | `confirm` | **Implemented** | + | `confirmation_method` | **Implemented** | + | `return_url` | **Implemented** | | `account_balance` | Not implemented | | `business_vat_id` | Not implemented | | `coupon` | Not implemented | @@ -345,7 +348,11 @@ defmodule Gringotts.Gateways.Stripe do [capture: true] String.starts_with?(payment, "pm") -> - [confirm: true, confirmation_method: "manual"] + [ + confirm: true, + confirmation_method: "manual", + return_url: opts[:return_url] + ] true -> [] @@ -483,6 +490,7 @@ defmodule Gringotts.Gateways.Stripe do token: data["client_secret"], message: data["latest_charge"], reason: data["status"], + next_action: data["next_action"], fraud_review: nil, cvc_result: nil, avs_result: nil diff --git a/lib/gringotts/response.ex b/lib/gringotts/response.ex index c71f2886..f33f44c8 100644 --- a/lib/gringotts/response.ex +++ b/lib/gringotts/response.ex @@ -19,7 +19,8 @@ defmodule Gringotts.Response do :cvc_result, :raw, :fraud_review, - :status + :status, + :next_action ] @typedoc """ @@ -44,8 +45,11 @@ defmodule Gringotts.Response do case of error. `nil` otherwise. | | `raw` | `String.t` | Raw response from the gateway. | | `fraud_review` | `term` | Gateway's risk assessment of the\ - transaction. | - | `status` | `String.t` | Status of the performed action. | + transaction. | + | `status` | `String.t` | Status of the performed action. | + | `next_action` | `map` | Necesssary data to perform other\ + actions in order to complete\ + payment` | ## Notes @@ -77,7 +81,8 @@ defmodule Gringotts.Response do cvc_result: String.t(), raw: String.t(), fraud_review: term, - status: String.t() + status: String.t(), + next_action: Map.t() } def success(opts \\ []) do diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index edca7afd..2b2d6007 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -34,7 +34,7 @@ defmodule Gringotts.Gateways.StripeTest do @present_opts [ description: "best purchase ever", receipt_email: "something@example.com", - # return_url: "http://localhost:5000/api/3ds2" + return_url: "http://localhost:5000/api/3ds2" ] describe "authorize/3" do @@ -59,6 +59,8 @@ defmodule Gringotts.Gateways.StripeTest do assert response.message == nil assert response.reason == "requires_source_action" assert response.fraud_review == nil + assert Map.has_key?(response.next_action, "redirect_to_url") + assert response.next_action["redirect_to_url"]["return_url"] == "http://localhost:5000/api/3ds2" assert String.contains?(response.raw, "\"amount\": 500") assert String.contains?(response.raw, "\"amount_capturable\": 0") assert String.contains?(response.raw, "\"confirmation_method\": \"manual\"") From 62db2e389ea9bcb376eb669f249f81b9f7cb43de Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Thu, 19 Oct 2023 10:10:12 +0200 Subject: [PATCH 6/7] [] make return_url optional --- lib/gringotts/gateways/stripe.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index aba65629..a0a86b15 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -350,9 +350,9 @@ defmodule Gringotts.Gateways.Stripe do String.starts_with?(payment, "pm") -> [ confirm: true, - confirmation_method: "manual", - return_url: opts[:return_url] + confirmation_method: "manual" ] + |> append_if(opts[:return_url], return_url: opts[:return_url]) true -> [] @@ -524,4 +524,8 @@ defmodule Gringotts.Gateways.Stripe do _ -> respond(response, :payment_intent) end end + + defp append_if(list, condition, item) do + if condition, do: list ++ item, else: list + end end From b862699de4f0467e8a43859d4fd06cd4e12309f4 Mon Sep 17 00:00:00 2001 From: Tommaso Patrizi Date: Wed, 31 Jan 2024 00:08:08 +0100 Subject: [PATCH 7/7] Update httpoison Allow nested form params: https://github.com/edgurgel/httpoison/issues/469 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 5f2dcca0..55f3ab42 100644 --- a/mix.exs +++ b/mix.exs @@ -56,7 +56,7 @@ defmodule Gringotts.Mixfile do # Type `mix help deps` for more examples and options defp deps do [ - {:httpoison, "~> 1.8"}, + {:httpoison, "~> 2.2"}, {:jason, "~> 1.4"}, {:xml_builder, "~> 2.2"}, {:elixir_xml_to_map, "~> 3.0"},