Skip to content

Commit

Permalink
Merge branch 'master' of github.com:whitepaperclip/stripe_mock
Browse files Browse the repository at this point in the history
  • Loading branch information
nkezhaya committed Jun 15, 2019
2 parents 471efff + 42b343d commit 7da8c9b
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 61 deletions.
10 changes: 3 additions & 7 deletions lib/stripe_mock/api/card.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
defmodule StripeMock.API.Card do
use Ecto.Schema
import Ecto.Changeset
alias StripeMock.API
use StripeMock.Schema

schema "cards" do
field :brand, :string
field :created, :integer
field :deleted, :boolean
field :metadata, StripeMock.Metadata, default: %{}
field :metadata, StripeMock.Type.Metadata, default: %{}
field :last4, :string
field :source, :string

Expand All @@ -24,8 +22,6 @@ defmodule StripeMock.API.Card do
card
|> cast(attrs, [:source, :metadata])
|> validate_required([:source, :metadata])
|> set_brand()
|> set_last4()
end

@doc false
Expand Down Expand Up @@ -69,7 +65,7 @@ defmodule StripeMock.API.Card do
defp set_last4(changeset) do
case get_field(changeset, :number) do
number when is_bitstring(number) ->
last4 = String.split_at(number, -6) |> elem(1)
last4 = String.split_at(number, -4) |> elem(1)
put_change(changeset, :last4, last4)

_ ->
Expand Down
83 changes: 78 additions & 5 deletions lib/stripe_mock/api/charge.ex
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
defmodule StripeMock.API.Charge do
use Ecto.Schema
import Ecto.Changeset

alias StripeMock.API
use StripeMock.Schema
alias StripeMock.Repo

@foreign_key_type :binary_id
schema "charges" do
field :amount, :integer
field :capture, :boolean, default: false
field :currency, :string
field :description, :string
field :metadata, StripeMock.Metadata, default: %{}
field :metadata, StripeMock.Type.Metadata, default: %{}
field :statement_descriptor, :string
field :transfer_group, :string

belongs_to :customer, API.Customer
belongs_to :source, API.Card
end

@doc false
Expand All @@ -27,10 +26,13 @@ defmodule StripeMock.API.Charge do
:customer_id,
:description,
:metadata,
:source_id,
:statement_descriptor,
:transfer_group
])
|> validate_required([:amount, :currency])
|> set_customer_and_source()
|> validate_required(:source_id)
end

@doc false
Expand All @@ -44,4 +46,75 @@ defmodule StripeMock.API.Charge do
])
|> validate_required([:amount, :currency])
end

defp set_customer_and_source(changeset) do
customer =
with customer_id when not is_nil(customer_id) <- get_field(changeset, :customer_id) do
case Repo.fetch(API.Customer, customer_id) do
{:ok, customer} -> customer
_ -> throw({:not_found, :customer_id})
end
else
_ -> nil
end

source =
case fetch_source(get_field(changeset, :source_id)) do
:invalid -> throw({:not_found, :source_id})
source -> source
end

case {get_field(changeset, :customer_id), get_field(changeset, :source_id)} do
{nil, nil} ->
validate_required(changeset, :source_id)

{nil, _source_id} ->
case source do
%{card: %{customer_id: nil}} -> changeset
_ -> validate_required(changeset, :customer_id)
end

{customer_id, nil} ->
# TODO: Get the default payment method.
API.Card
|> Repo.all()
|> Enum.filter(&(&1.customer_id == customer_id))
|> case do
[source | _] -> put_change(changeset, :source_id, source.id)
[] -> throw({:not_found, :source_id})
end

{nil, "card_" <> _} ->
throw({:not_found, :source_id})

{_customer_id, "card_" <> _} ->
if source.customer_id != customer.id do
throw({:not_found, :source_id})
else
changeset
end

{_customer_id, "tok_" <> _} ->
changeset

_ ->
add_error(changeset, :base, "either source or customer is required")
end
|> ensure_source()
catch
{:not_found, field} -> add_error(changeset, field, "not found")
end

defp ensure_source(changeset) do
with source when is_map(source) <- fetch_source(get_field(changeset, :source_id)) do
changeset
else
_ -> add_error(changeset, :source, "is invalid")
end
end

defp fetch_source("tok_" <> _ = token_id), do: Repo.get(API.Token, token_id)
defp fetch_source("card_" <> _ = card_id), do: Repo.get(API.Card, card_id)
defp fetch_source(nil), do: nil
defp fetch_source(_), do: :invalid
end
5 changes: 2 additions & 3 deletions lib/stripe_mock/api/customer.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule StripeMock.API.Customer do
use Ecto.Schema
import Ecto.Changeset
use StripeMock.Schema

@primary_key {:id, :binary_id, autogenerate: false}
schema "customers" do
Expand All @@ -9,7 +8,7 @@ defmodule StripeMock.API.Customer do
field :deleted, :boolean, default: false
field :description, :string
field :email, :string
field :metadata, StripeMock.Metadata, default: %{}
field :metadata, StripeMock.Type.Metadata, default: %{}
field :name, :string
field :phone, :string
end
Expand Down
1 change: 1 addition & 0 deletions lib/stripe_mock/api/operations/card.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule StripeMock.API.Operations.Card do
|> Enum.filter(&(&1.customer_id == customer.id))
end

def get_card(id), do: Repo.get(Card, id)
def get_card!(id), do: Repo.get!(Card, id)

def create_card(customer, attrs) do
Expand Down
39 changes: 35 additions & 4 deletions lib/stripe_mock/api/operations/charge.ex
Original file line number Diff line number Diff line change
@@ -1,24 +1,55 @@
defmodule StripeMock.API.Operations.Charge do
alias StripeMock.Repo
alias StripeMock.API.Charge
alias StripeMock.API.{Card, Charge, Token}

def list_charges do
Repo.all(Charge)
def list_charges() do
Charge
|> Repo.all()
|> preload_source()
end

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

defp preload_source(charges) when is_list(charges) do
Enum.map(charges, &preload_source/1)
end

defp preload_source({:ok, charge}) do
{:ok, preload_source(charge)}
end

defp preload_source(%Charge{} = charge) do
%{charge | source: fetch_source(charge.source_id)}
end

defp preload_source(any), do: any

def create_charge(attrs \\ %{}) do
%Charge{}
|> Charge.create_changeset(attrs)
|> Repo.insert()
|> preload_source()
end

def update_charge(%Charge{} = charge, attrs) do
charge
|> Charge.create_changeset(attrs)
|> Repo.update()
|> preload_source()
end

defp fetch_source("card_" <> _ = card_id), do: Repo.get(Card, card_id)

defp fetch_source("tok_" <> _ = token_id) do
case Repo.get(Token, token_id) do
%{card_id: card_id} -> fetch_source(card_id)
_ -> nil
end
end

defp fetch_source(nil), do: nil
end
8 changes: 3 additions & 5 deletions lib/stripe_mock/api/refund.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
defmodule StripeMock.API.Refund do
use Ecto.Schema
import Ecto.Changeset

alias StripeMock.{API, Repo}
use StripeMock.Schema
alias StripeMock.Repo

@foreign_key_type :binary_id
schema "refunds" do
field :amount, :integer
field :created, :integer
field :metadata, StripeMock.Metadata, default: %{}
field :metadata, StripeMock.Type.Metadata, default: %{}
field :reason, :string

belongs_to :charge, API.Charge
Expand Down
8 changes: 3 additions & 5 deletions lib/stripe_mock/api/token.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule StripeMock.API.Token do
use Ecto.Schema
import Ecto.Changeset
alias StripeMock.API
use StripeMock.Schema

@foreign_key_type :binary_id
schema "tokens" do
Expand All @@ -16,10 +14,10 @@ defmodule StripeMock.API.Token do
@doc false
def changeset(token, attrs) do
token
|> cast(attrs, [:client_ip])
|> cast(attrs, [:client_ip, :type])
|> cast_assoc(:card, with: &API.Card.token_changeset/2)
|> set_type()
|> validate_required([:client_ip, :type])
|> validate_required([:type])
end

defp set_type(changeset) do
Expand Down
37 changes: 27 additions & 10 deletions lib/stripe_mock/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule StripeMock.Repo do
State structure is:
%{
customers: %{
customer: %{
"cus_123123" => %Customer{}
}
}
Expand All @@ -27,7 +27,7 @@ defmodule StripeMock.Repo do

def get!(schema, id) do
case GenServer.call(pid(), {:get, schema, id}) do
nil -> raise "Not found."
nil -> raise "No #{schema} found with id #{id}."
record -> record
end
end
Expand Down Expand Up @@ -55,7 +55,7 @@ defmodule StripeMock.Repo do

@impl true
def handle_call({:all, schema}, _from, state) do
with schemas when is_map(schemas) <- Map.get(state, schema),
with schemas when is_map(schemas) <- Map.get(state, type(schema)),
objects <- Map.values(schemas),
not_deleted <-
Enum.filter(objects, fn
Expand All @@ -79,7 +79,7 @@ defmodule StripeMock.Repo do
@impl true
def handle_call({:get, schema, id}, _from, state) do
object =
with schemas when is_map(schemas) <- Map.get(state, schema),
with schemas when is_map(schemas) <- Map.get(state, type(schema)),
object when is_map(object) <- Map.get(schemas, id) do
object
else
Expand Down Expand Up @@ -118,6 +118,8 @@ defmodule StripeMock.Repo do
changeset
end

changeset = set_client_ip(changeset)

# Run through changed assocs and save
{changeset, state} =
Enum.reduce_while(changeset.changes, {changeset, state}, fn
Expand Down Expand Up @@ -147,7 +149,7 @@ defmodule StripeMock.Repo do
object = apply_changes(changeset)

# Put the object into the new state
type = object.__struct__
type = type(object)

type_map =
case Map.get(state, type) do
Expand All @@ -164,15 +166,30 @@ defmodule StripeMock.Repo do
{:error, changeset}
end

if Mix.env() == :test do
defp set_client_ip(changeset) do
if Map.has_key?(changeset.data, :client_ip) and is_nil(get_field(changeset, :client_ip)) do
put_change(changeset, :client_ip, "0.0.0.0")
else
changeset
end
end
else
defp set_client_ip(changeset) do
changeset
end
end

defp generate_id(schema) do
prefix(schema) <> "_" <> StripeMock.ID.generate()
end

def type(%API.Card{}), do: :card
def type(%API.Charge{}), do: :charge
def type(%API.Customer{}), do: :customer
def type(%API.Refund{}), do: :refund
def type(%API.Token{}), do: :token
def type(%{} = map), do: type(map.__struct__)
def type(API.Card), do: :card
def type(API.Charge), do: :charge
def type(API.Customer), do: :customer
def type(API.Refund), do: :refund
def type(API.Token), do: :token

def prefix(%API.Card{}), do: "card"
def prefix(%API.Charge{}), do: "ch"
Expand Down
9 changes: 9 additions & 0 deletions lib/stripe_mock/schema.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule StripeMock.Schema do
defmacro __using__(_) do
quote do
use Ecto.Schema
import Ecto.Changeset, warn: false
alias StripeMock.API
end
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule StripeMock.Metadata do
defmodule StripeMock.Type.Metadata do
@behaviour Ecto.Type

def type(), do: :map
Expand Down
4 changes: 3 additions & 1 deletion lib/stripe_mock_web/controllers/charge_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ defmodule StripeMockWeb.ChargeController do
alias StripeMock.API
alias StripeMock.API.Charge

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

action_fallback StripeMockWeb.FallbackController

def index(conn, params) do
Expand Down
Loading

0 comments on commit 7da8c9b

Please sign in to comment.