Skip to content

Commit

Permalink
Initial payment intents API complete
Browse files Browse the repository at this point in the history
  • Loading branch information
nkezhaya committed Oct 16, 2019
1 parent 67e34d8 commit 9ff8cdf
Show file tree
Hide file tree
Showing 18 changed files with 392 additions and 49 deletions.
6 changes: 2 additions & 4 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use Mix.Config

config :stripe_mock, StripeMockWeb.Endpoint,
server: false

# Print only warnings and errors during test
config :stripe_mock, StripeMockWeb.Endpoint, server: false
config :stripe_mock, StripeMock.Repo, pool: Ecto.Adapters.SQL.Sandbox
config :logger, level: :warn
1 change: 0 additions & 1 deletion lib/stripe_mock/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ defmodule StripeMock.API do
# PaymentIntents
defdelegate list_payment_intents(), to: Ops.PaymentIntent
defdelegate get_payment_intent(id), to: Ops.PaymentIntent
defdelegate get_payment_intent!(id), to: Ops.PaymentIntent
defdelegate create_payment_intent(attrs \\ %{}), to: Ops.PaymentIntent
defdelegate update_payment_intent(payment_intent, attrs \\ %{}), to: Ops.PaymentIntent
defdelegate confirm_payment_intent(payment_intent), to: Ops.PaymentIntent
Expand Down
8 changes: 5 additions & 3 deletions lib/stripe_mock/api/charge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ defmodule StripeMock.API.Charge do
field :amount, :integer
field :capture, :boolean, default: false
field :currency, :string
field :description, :string
field :metadata, :map, default: %{}
field :statement_descriptor, :string
field :transfer_group, :string

field :source, :string, virtual: true

belongs_to :payment_intent, API.PaymentIntent
belongs_to :customer, API.Customer
belongs_to :card, API.Card
belongs_to :token, API.Token

common_fields()
timestamps()
end

Expand Down Expand Up @@ -50,7 +50,9 @@ defmodule StripeMock.API.Charge do
end

@doc false
def capture_changeset(payment_intent, charge) do
def capture_changeset(charge, payment_intent) do
charge
|> change(%{payment_intent_id: payment_intent.id})
end

defp set_customer_and_source(changeset) do
Expand Down
8 changes: 4 additions & 4 deletions lib/stripe_mock/api/operations/card.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule StripeMock.API.Operations.Card do
import Ecto.Query
alias Ecto.Multi
alias StripeMock.Repo
alias StripeMock.API.{Card, Source}
alias StripeMock.API.{Card, PaymentMethod}

def list_cards(customer) do
Card
Expand All @@ -17,9 +17,9 @@ defmodule StripeMock.API.Operations.Card do
def create_card(customer, attrs) do
Multi.new()
|> Multi.insert(:card, Card.create_changeset(%Card{customer_id: customer.id}, attrs))
|> Multi.run(:source, fn _repo, %{card: card} ->
%Source{}
|> Source.changeset(%{card_id: card.id})
|> Multi.run(:payment_method, fn _repo, %{card: card} ->
%PaymentMethod{}
|> PaymentMethod.changeset(%{card_id: card.id})
|> Repo.insert()
end)
|> Repo.transaction()
Expand Down
6 changes: 3 additions & 3 deletions lib/stripe_mock/api/operations/charge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ defmodule StripeMock.API.Operations.Charge do
end

def get_charge(id) do
with {:ok, charge} <- Repo.fetch(Charge, id) do
{:ok, preload_source(charge)}
end
Charge
|> Repo.fetch(id)
|> preload_source()
end

def create_charge(attrs \\ %{}) do
Expand Down
38 changes: 33 additions & 5 deletions lib/stripe_mock/api/operations/payment_intent.ex
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
defmodule StripeMock.API.Operations.PaymentIntent do
import Ecto.Query

alias StripeMock.Repo
alias StripeMock.API.{Charge, PaymentIntent}

@preload [payment_method: [:card, :source, token: [:card]]]

def list_payment_intents() do
Repo.all(PaymentIntent)
PaymentIntent
|> preload(^@preload)
|> Repo.all()
end

def get_payment_intent(id), do: Repo.fetch(PaymentIntent, id)
def get_payment_intent!(id), do: Repo.get!(PaymentIntent, id)
def get_payment_intent(id) do
PaymentIntent
|> preload(^@preload)
|> Repo.fetch(id)
|> preload_payment_method()
end

def create_payment_intent(attrs \\ %{}) do
def create_payment_intent(attrs) do
%PaymentIntent{}
|> PaymentIntent.changeset(attrs)
|> Repo.insert()
|> preload_payment_method()
end

def update_payment_intent(%PaymentIntent{} = payment_intent, attrs) do
payment_intent
|> PaymentIntent.changeset(attrs)
|> Repo.update()
|> preload_payment_method()
end

def confirm_payment_intent(%PaymentIntent{} = payment_intent) do
payment_intent
|> PaymentIntent.status_changeset("requires_capture")
|> Repo.update()
|> preload_payment_method()
end

def capture_payment_intent(%PaymentIntent{} = payment_intent) do
charge =
%Charge{}
|> Charge.payment_intent_changeset(payment_intent)
|> Charge.capture_changeset(payment_intent)
|> Repo.insert!()

payment_intent
|> PaymentIntent.capture_changeset(charge)
|> Repo.update!()
|> preload_payment_method()
end

defp preload_payment_method({:ok, payment_intent}) do
{:ok, preload_payment_method(payment_intent)}
end

defp preload_payment_method(%PaymentIntent{} = payment_intent) do
Repo.preload(payment_intent, @preload)
end

defp preload_payment_method([_ | _] = payment_intents) do
Repo.preload(payment_intents, @preload)
end

defp preload_payment_method(arg), do: arg
end
8 changes: 4 additions & 4 deletions lib/stripe_mock/api/operations/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule StripeMock.API.Operations.Token do

alias Ecto.Multi
alias StripeMock.Repo
alias StripeMock.API.{Token, Source}
alias StripeMock.API.{Token, PaymentMethod}

def get_token(id) do
Token
Expand All @@ -14,9 +14,9 @@ defmodule StripeMock.API.Operations.Token do
def create_token(attrs) do
Multi.new()
|> Multi.insert(:token, Token.changeset(%Token{}, attrs))
|> Multi.run(:source, fn _repo, %{token: token} ->
%Source{}
|> Source.changeset(%{token_id: token.id})
|> Multi.run(:payment_method, fn _repo, %{token: token} ->
%PaymentMethod{}
|> PaymentMethod.changeset(%{token_id: token.id})
|> Repo.insert()
end)
|> Repo.transaction()
Expand Down
66 changes: 58 additions & 8 deletions lib/stripe_mock/api/payment_intent.ex
Original file line number Diff line number Diff line change
@@ -1,32 +1,82 @@
defmodule StripeMock.API.PaymentIntent do
use StripeMock.Schema

@primary_key {:id, :binary_id, autogenerate: false}
schema "payment_intents" do
field :amount, :integer
field :capture, :boolean, default: false
field :capture_method, :string
field :confirmation_method, :string
field :capture_method, :string, default: "automatic"
field :confirm, :boolean, virtual: true, default: false
field :confirmation_method, :string, default: "automatic"
field :currency, :string
field :description, :string
field :metadata, StripeMock.Type.Metadata, default: %{}
field :payment_method_types, {:array, :string}, default: ["card"]
field :statement_descriptor, :string
field :status, :string
field :transfer_data, :map
field :transfer_group, :string

belongs_to(:customer, API.Customer)
belongs_to(:payment_method, API.PaymentMethod)
has_many(:charges, API.Charge)

common_fields()
timestamps()
end

@doc false
def changeset(payment_intent, attrs) do
payment_intent
|> cast(attrs, [:amount, :confirm, :confirmation_method, :currency])
|> cast(attrs, [
:amount,
:confirm,
:confirmation_method,
:currency,
:customer_id,
:description,
:metadata,
:payment_method_id,
:statement_descriptor,
:transfer_data,
:transfer_group
])
|> validate_inclusion(:capture_method, ~w(automatic manual))
|> validate_inclusion(:confirmation_method, ~w(automatic manual))
|> set_payment_method()
|> validate_required([:payment_method_id])
|> put_common_fields()
end

@doc false
def status_changeset(payment_intent, status) do
change(payment_intent, %{status: status})
payment_intent
|> change(%{status: status})
|> put_common_fields()
end

@doc false
def capture_changeset(payment_intent, charge) do
end

defp set_payment_method(changeset) do
case get_change(changeset, :payment_method_id) do
nil ->
changeset

id ->
card = Repo.get(API.Card, id)
token = Repo.get(API.Token, id)

case find_payment_method(card || token) do
nil -> add_error(changeset, :payment_method_id, "not found")
payment_method -> put_change(changeset, :payment_method_id, payment_method.id)
end
end
end

defp find_payment_method(%API.Card{} = card),
do: Repo.get_by(API.PaymentMethod, card_id: card.id)

defp find_payment_method(%API.Token{} = token),
do: Repo.get_by(API.PaymentMethod, token_id: token.id)

defp find_payment_method(_),
do: nil
end
19 changes: 19 additions & 0 deletions lib/stripe_mock/api/payment_method.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule StripeMock.API.PaymentMethod do
use StripeMock.Schema

schema "payment_methods" do
belongs_to :card, API.Card
belongs_to :token, API.Token
belongs_to :source, API.Source

common_fields()
timestamps()
end

@doc false
def changeset(token, attrs) do
token
|> cast(attrs, [:card_id, :token_id, :source_id])
|> put_common_fields()
end
end
12 changes: 8 additions & 4 deletions lib/stripe_mock/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ defmodule StripeMock.Schema do
end
end

defmacro common_fields() do
quote do
field :description, :string
field :metadata, :map, default: %{}
end
end

import Ecto.Changeset

def put_common_fields(changeset) do
case get_field(changeset, :metadata) do
nil -> put_change(changeset, :metadata, %{})
_ -> changeset
end
validate_required(changeset, [:metadata])
end
end
24 changes: 12 additions & 12 deletions lib/stripe_mock_web/controllers/payment_intent_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ defmodule StripeMockWeb.PaymentIntentController do
use StripeMockWeb, :controller

alias StripeMock.API
alias StripeMock.API.PaymentIntent

plug SMPlug.ConvertParams,
%{"customer" => "customer_id", "payment_method" => "payment_method_id"}
when action in [:create, :update]

action_fallback StripeMockWeb.FallbackController

Expand All @@ -27,26 +30,23 @@ defmodule StripeMockWeb.PaymentIntentController do
end

def update(conn, %{"id" => id} = payment_intent_params) do
payment_intent = API.get_payment_intent!(id)

with {:ok, payment_intent} <-
with {:ok, payment_intent} <- API.get_payment_intent(id),
{:ok, payment_intent} <-
API.update_payment_intent(payment_intent, payment_intent_params) do
render(conn, "show.json", payment_intent: payment_intent)
end
end

def confirm(conn, %{"id" => id} = payment_intent_params) do
payment_intent = API.get_payment_intent!(id)

with {:ok, payment_intent} <- API.confirm_payment_intent(payment_intent) do
def confirm(conn, %{"id" => id}) do
with {:ok, payment_intent} <- API.get_payment_intent(id),
{:ok, payment_intent} <- API.confirm_payment_intent(payment_intent) do
render(conn, "show.json", payment_intent: payment_intent)
end
end

def capture(conn, %{"id" => id} = payment_intent_params) do
payment_intent = API.get_payment_intent!(id)

with {:ok, payment_intent} <- API.capture_payment_intent(payment_intent) do
def capture(conn, %{"id" => id}) do
with {:ok, payment_intent} <- API.get_payment_intent(id),
{:ok, payment_intent} <- API.capture_payment_intent(payment_intent) do
render(conn, "show.json", payment_intent: payment_intent)
end
end
Expand Down
29 changes: 29 additions & 0 deletions lib/stripe_mock_web/views/payment_intent_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule StripeMockWeb.PaymentIntentView do
use StripeMockWeb, :view
alias StripeMockWeb.PaymentIntentView

def render("index.json", %{page: page}) do
%{data: render_many(page.data, PaymentIntentView, "payment_intent.json")}
end

def render("show.json", %{payment_intent: payment_intent}) do
render_one(payment_intent, PaymentIntentView, "payment_intent.json")
end

def render("payment_intent.json", %{payment_intent: payment_intent}) do
payment_intent
|> as_map()
|> Map.take(
~w(amount capture_method confirmation_method currency description id metadata payment_method_types statement_descriptor status transfer_group)a
)
|> Map.put("customer", payment_intent.customer_id)
|> Map.merge(%{
object: "payment_intent",
payment_method: render_payment_method(payment_intent.payment_method)
})
end

def render_payment_method(payment_method) do
render(StripeMockWeb.PaymentMethodView, "payment_method.json", payment_method: payment_method)
end
end
Loading

0 comments on commit 9ff8cdf

Please sign in to comment.