diff --git a/config/config.exs b/config/config.exs index 0a8e523..12dae8f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,8 +12,9 @@ config :stripe_mock, StripeMockWeb.Endpoint, url: [host: "localhost"], http: [:inet6, port: System.get_env("PORT") || 12111], secret_key_base: "ug9ATr9o7f/N2inxnW+SlrNVP7Ok+f9gAP43yHfqqm/bgFZSLeY6vQOY+wp562Iz", - render_errors: [view: StripeMockWeb.ErrorView, accepts: ~w(json)], - pubsub: [name: StripeMock.PubSub, adapter: Phoenix.PubSub.PG2] + render_errors: [view: StripeMockWeb.ErrorView, accepts: ~w(json)] + +config :stripe_mock, ecto_repos: [StripeMock.Repo] # Configures Elixir's Logger config :logger, :console, @@ -23,6 +24,20 @@ config :logger, :console, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +{uri, _} = System.cmd("pg_tmp", ["-t"]) + +[username, host, port, database] = + Regex.scan(~r/(\w+)@([\w\d\.]+)\:(\d+)\/(\w+)/i, uri, capture: :all_but_first) |> List.flatten() + +config :stripe_mock, StripeMock.Repo, + username: username, + password: "", + database: database, + hostname: host, + port: port, + pool_size: 2, + migration_primary_key: [name: :id, type: :binary_id] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/lib/stripe_mock/api/card.ex b/lib/stripe_mock/api/card.ex index 0e2e67a..8359837 100644 --- a/lib/stripe_mock/api/card.ex +++ b/lib/stripe_mock/api/card.ex @@ -3,9 +3,8 @@ defmodule StripeMock.API.Card do schema "cards" do field :brand, :string - field :created, :integer field :deleted, :boolean - field :metadata, StripeMock.Type.Metadata, default: %{} + field :metadata, :map, default: %{} field :last4, :string field :source, :string @@ -15,6 +14,8 @@ defmodule StripeMock.API.Card do field :cvc, :string belongs_to :customer, API.Customer + + timestamps() end @doc false diff --git a/lib/stripe_mock/api/charge.ex b/lib/stripe_mock/api/charge.ex index 427e095..7cf1340 100644 --- a/lib/stripe_mock/api/charge.ex +++ b/lib/stripe_mock/api/charge.ex @@ -1,19 +1,22 @@ defmodule StripeMock.API.Charge do 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.Type.Metadata, default: %{} + field :metadata, :map, default: %{} field :statement_descriptor, :string field :transfer_group, :string + field :source, :string, virtual: true + belongs_to :customer, API.Customer - belongs_to :source, API.Card + belongs_to :card, API.Card + belongs_to :token, API.Token + + timestamps() end @doc false @@ -26,25 +29,24 @@ defmodule StripeMock.API.Charge do :customer_id, :description, :metadata, - :source_id, + :source, :statement_descriptor, :transfer_group ]) - |> validate_required([:amount, :currency]) |> set_customer_and_source() - |> validate_required(:source_id) + |> validate_required([:amount, :currency, :card_id]) + |> put_common_fields() end @doc false def update_changeset(charge, attrs) do charge |> cast(attrs, [ - :customer_id, :description, :metadata, :transfer_group ]) - |> validate_required([:amount, :currency]) + |> put_common_fields() end @doc false @@ -52,73 +54,38 @@ defmodule StripeMock.API.Charge do 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}) + case fetch_source(get_field(changeset, :source)) do + nil -> throw({:not_found, :source}) 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 + case source do + nil -> + add_error(changeset, :base, "either source or customer is required") - {_customer_id, "tok_" <> _} -> + %API.Card{} -> changeset + |> put_change(:card_id, source.id) + |> validate_required(:customer_id) - _ -> - add_error(changeset, :base, "either source or customer is required") + %API.Token{} -> + changeset + |> put_change(:token_id, source.id) + |> put_change(:card_id, source.card_id) 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 + defp fetch_source(nil) do + nil 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 + defp fetch_source(source) do + card = Repo.get(API.Card, source) + token = Repo.get(API.Token, source) + + card || token + end end diff --git a/lib/stripe_mock/api/customer.ex b/lib/stripe_mock/api/customer.ex index 29685c1..c57d655 100644 --- a/lib/stripe_mock/api/customer.ex +++ b/lib/stripe_mock/api/customer.ex @@ -1,16 +1,16 @@ defmodule StripeMock.API.Customer do use StripeMock.Schema - @primary_key {:id, :binary_id, autogenerate: false} schema "customers" do - field :created, :integer field :currency, :string, default: "usd" field :deleted, :boolean, default: false field :description, :string field :email, :string - field :metadata, StripeMock.Type.Metadata, default: %{} + field :metadata, :map, default: %{} field :name, :string field :phone, :string + + timestamps() end @doc false diff --git a/lib/stripe_mock/api/operations/card.ex b/lib/stripe_mock/api/operations/card.ex index 7c9a292..5e0a136 100644 --- a/lib/stripe_mock/api/operations/card.ex +++ b/lib/stripe_mock/api/operations/card.ex @@ -1,26 +1,38 @@ defmodule StripeMock.API.Operations.Card do + import Ecto.Query + alias Ecto.Multi alias StripeMock.Repo - alias StripeMock.API.Card + alias StripeMock.API.{Card, Source} def list_cards(customer) do Card + |> where([c], not c.deleted) + |> where([c], c.customer_id == ^customer.id) |> Repo.all() - |> Enum.filter(&(&1.customer_id == customer.id)) end def get_card(id), do: Repo.fetch(Card, id) def get_card!(id), do: Repo.get!(Card, id) def create_card(customer, attrs) do - %Card{customer_id: customer.id} - |> Card.create_changeset(attrs) - |> Repo.insert() + 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}) + |> Repo.insert() + end) + |> Repo.transaction() + |> case do + {:ok, %{card: card}} -> {:ok, card} + {:error, _, value, _} -> {:error, value} + end end def create_customer_card_from_source(customer, source, metadata) do result = source.card - |> Ecto.Changeset.change(%{customer_id: customer.id, metadata: metadata}) + |> Ecto.Changeset.change(%{customer_id: customer.id, metadata: metadata || %{}}) |> Repo.update() source diff --git a/lib/stripe_mock/api/operations/charge.ex b/lib/stripe_mock/api/operations/charge.ex index 18faa66..3767c27 100644 --- a/lib/stripe_mock/api/operations/charge.ex +++ b/lib/stripe_mock/api/operations/charge.ex @@ -1,6 +1,6 @@ defmodule StripeMock.API.Operations.Charge do alias StripeMock.Repo - alias StripeMock.API.{Card, Charge, Token} + alias StripeMock.API.Charge def list_charges() do Charge @@ -14,20 +14,6 @@ defmodule StripeMock.API.Operations.Charge do 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) @@ -37,19 +23,22 @@ defmodule StripeMock.API.Operations.Charge do def update_charge(%Charge{} = charge, attrs) do charge - |> Charge.create_changeset(attrs) + |> Charge.update_changeset(attrs) |> Repo.update() |> preload_source() end - defp fetch_source("card_" <> _ = card_id), do: Repo.get(Card, card_id) + defp preload_source({:ok, charge}) do + {:ok, preload_source(charge)} + end - defp fetch_source("tok_" <> _ = token_id) do - case Repo.get(Token, token_id) do - %{card_id: card_id} -> fetch_source(card_id) - _ -> nil - end + defp preload_source(%Charge{} = charge) do + Repo.preload(charge, [:card, :token]) + end + + defp preload_source([_ | _] = charges) do + Repo.preload(charges, [:card, :token]) end - defp fetch_source(nil), do: nil + defp preload_source(arg), do: arg end diff --git a/lib/stripe_mock/api/operations/token.ex b/lib/stripe_mock/api/operations/token.ex index 52ea324..f12e1ca 100644 --- a/lib/stripe_mock/api/operations/token.ex +++ b/lib/stripe_mock/api/operations/token.ex @@ -1,14 +1,28 @@ defmodule StripeMock.API.Operations.Token do + import Ecto.Query + + alias Ecto.Multi alias StripeMock.Repo - alias StripeMock.API.Token + alias StripeMock.API.{Token, Source} def get_token(id) do - Repo.fetch(Token, id) + Token + |> preload(:card) + |> Repo.fetch(id) end def create_token(attrs) do - %Token{} - |> Token.changeset(attrs) - |> Repo.insert() + Multi.new() + |> Multi.insert(:token, Token.changeset(%Token{}, attrs)) + |> Multi.run(:source, fn _repo, %{token: token} -> + %Source{} + |> Source.changeset(%{token_id: token.id}) + |> Repo.insert() + end) + |> Repo.transaction() + |> case do + {:ok, %{token: token}} -> {:ok, token} + {:error, _, value, _} -> {:error, value} + end end end diff --git a/lib/stripe_mock/api/refund.ex b/lib/stripe_mock/api/refund.ex index 8aa5d7f..5ff58bf 100644 --- a/lib/stripe_mock/api/refund.ex +++ b/lib/stripe_mock/api/refund.ex @@ -2,14 +2,14 @@ defmodule StripeMock.API.Refund do use StripeMock.Schema alias StripeMock.Repo - @foreign_key_type :binary_id schema "refunds" do field :amount, :integer - field :created, :integer - field :metadata, StripeMock.Type.Metadata, default: %{} + field :metadata, :map, default: %{} field :reason, :string belongs_to :charge, API.Charge + + timestamps() end @doc false diff --git a/lib/stripe_mock/api/source.ex b/lib/stripe_mock/api/source.ex new file mode 100644 index 0000000..dffbd2d --- /dev/null +++ b/lib/stripe_mock/api/source.ex @@ -0,0 +1,14 @@ +defmodule StripeMock.API.Source do + use StripeMock.Schema + + schema "sources" do + belongs_to :card, API.Card + belongs_to :token, API.Card + end + + @doc false + def changeset(token, attrs) do + token + |> cast(attrs, [:card_id, :token_id]) + end +end diff --git a/lib/stripe_mock/api/token.ex b/lib/stripe_mock/api/token.ex index f37e7c3..a3cced2 100644 --- a/lib/stripe_mock/api/token.ex +++ b/lib/stripe_mock/api/token.ex @@ -1,14 +1,14 @@ defmodule StripeMock.API.Token do use StripeMock.Schema - @foreign_key_type :binary_id schema "tokens" do field :client_ip, :string - field :created, :integer field :type, :string field :used, :boolean, default: false belongs_to :card, API.Card + + timestamps() end @doc false diff --git a/lib/stripe_mock/repo.ex b/lib/stripe_mock/repo.ex index 843d2d9..187555f 100644 --- a/lib/stripe_mock/repo.ex +++ b/lib/stripe_mock/repo.ex @@ -1,172 +1,31 @@ defmodule StripeMock.Repo do - @moduledoc """ - This is where we store everything. Obviously, don't call any of these - functions yourself. - - State structure is: - - %{ - customer: %{ - "cus_123123" => %Customer{} - } - } - """ - use GenServer + use Ecto.Repo, otp_app: :stripe_mock, adapter: Ecto.Adapters.Postgres import Ecto.Changeset alias Ecto.Changeset alias StripeMock.API - def start_link(opts \\ []) do - GenServer.start_link(__MODULE__, %{}, [name: __MODULE__] ++ opts) - end - - def all(schema), do: GenServer.call(pid(), {:all, schema}) - def insert(changeset), do: GenServer.call(pid(), {:save, changeset}) - def update(changeset), do: GenServer.call(pid(), {:save, changeset}) - def get(schema, id), do: GenServer.call(pid(), {:get, schema, id}) - - def get!(schema, id) do - case GenServer.call(pid(), {:get, schema, id}) do - nil -> - raise Ecto.NoResultsError, message: "No #{schema} found with id #{id}.", queryable: schema - - record -> - record - end - end - def fetch(schema, id) do - case get(schema, id) do - nil -> {:error, :not_found} - object -> {:ok, object} - end - end - - def delete(%Changeset{} = changeset) do - changeset = put_change(changeset, :deleted, true) - GenServer.call(pid(), {:save, changeset}) - end - - def delete(object), do: GenServer.call(pid(), {:save, %{object | deleted: true}}) - - defp pid(), do: GenServer.whereis(__MODULE__) - - @impl true - def init(state) do - {:ok, state} - end - - @impl true - def handle_call({:all, schema}, _from, state) do - with schemas when is_map(schemas) <- Map.get(state, type(schema)), - objects <- Map.values(schemas), - not_deleted <- - Enum.filter(objects, fn - %{deleted: true} -> false - _ -> true - end) do - {:reply, not_deleted, state} + with true <- valid_id?(id), + %{} = object <- get(schema, id) do + {:ok, object} else - _ -> {:reply, [], state} + _ -> {:error, :not_found} end end - @impl true - def handle_call({:save, changeset}, _from, state) do - case save(changeset, state) do - {:ok, term, new_state} -> {:reply, {:ok, term}, new_state} - {:error, _} = error -> {:reply, error, state} + defp valid_id?(id) do + case Ecto.UUID.cast(id) do + {:ok, _} -> true + _ -> false end end - @impl true - def handle_call({:get, schema, id}, _from, state) do - object = - with schemas when is_map(schemas) <- Map.get(state, type(schema)), - object when is_map(object) <- Map.get(schemas, id) do - object - else - _ -> nil - end - - {:reply, object, state} - end - - @spec save(Changeset.t(), map()) :: {:ok, any(), map()} | {:error, Changeset.t()} - defp save(changeset, state) do - changeset = change(changeset) - - changeset = - case get_field(changeset, :id) do - nil -> put_change(changeset, :id, generate_id(changeset.data)) - id when is_bitstring(id) -> changeset - end - - changeset = - cond do - Map.has_key?(changeset.data, :created) -> - case get_field(changeset, :created) do - nil -> put_change(changeset, :created, :os.system_time(:seconds)) - _ -> changeset - end - - true -> - changeset - end - - changeset = - if Map.has_key?(changeset.data, :object) do - put_change(changeset, :object, type(changeset.data)) - else - changeset - end - - changeset = set_client_ip(changeset) - - # Run through changed assocs and save - {changeset, state} = - Enum.reduce_while(changeset.changes, {changeset, state}, fn - {key, %Changeset{} = assoc_change}, {changeset, state} -> - case save(assoc_change, state) do - {:ok, saved_assoc, new_state} -> - changeset = - changeset - |> put_change(key, saved_assoc) - |> put_change(String.to_atom("#{key}_id"), saved_assoc.id) - - {:cont, {changeset, new_state}} - - {:error, failed_assoc} -> - changeset = put_change(changeset, key, failed_assoc) - throw({:invalid_changeset, changeset}) - end - - _, acc -> - {:cont, acc} - end) - - if !changeset.valid? do - throw({:invalid_changeset, changeset}) - end - - object = apply_changes(changeset) - - # Put the object into the new state - type = type(object) - - type_map = - case Map.get(state, type) do - %{} = s -> s - _ -> %{} - end - |> Map.put(object.id, object) - - new_state = Map.put(state, type, type_map) + defoverridable delete: 1, delete: 2 - {:ok, object, new_state} - catch - {:invalid_changeset, changeset} -> - {:error, changeset} + def delete(struct) do + struct + |> change(%{deleted: true}) + |> update() end if Mix.env() == :test do diff --git a/lib/stripe_mock/schema.ex b/lib/stripe_mock/schema.ex index aec9302..e232b1c 100644 --- a/lib/stripe_mock/schema.ex +++ b/lib/stripe_mock/schema.ex @@ -3,7 +3,22 @@ defmodule StripeMock.Schema do quote do use Ecto.Schema import Ecto.Changeset, warn: false - alias StripeMock.API + import Ecto.Query + import unquote(__MODULE__) + alias StripeMock.{API, Repo} + + Module.put_attribute(__MODULE__, :primary_key, {:id, :binary_id, autogenerate: true}) + Module.put_attribute(__MODULE__, :foreign_key_type, :binary_id) + Module.put_attribute(__MODULE__, :timestamps_opts, inserted_at: :created, updated_at: false) + end + end + + import Ecto.Changeset + + def put_common_fields(changeset) do + case get_field(changeset, :metadata) do + nil -> put_change(changeset, :metadata, %{}) + _ -> changeset end end end diff --git a/lib/stripe_mock/type/metadata.ex b/lib/stripe_mock/type/metadata.ex deleted file mode 100644 index a903918..0000000 --- a/lib/stripe_mock/type/metadata.ex +++ /dev/null @@ -1,27 +0,0 @@ -defmodule StripeMock.Type.Metadata do - @behaviour Ecto.Type - - def type(), do: :map - - def cast(nil), do: {:ok, %{}} - def cast(""), do: {:ok, %{}} - - def cast(metadata) when is_map(metadata) do - value = - for {k, v} <- metadata, - is_bitstring(k) and String.length(k) <= 40, - is_bitstring(v) and String.length(v) <= 500, - into: %{}, - do: {k, v} - - {:ok, value} - end - - def cast(_), do: :error - - def dump(value) when is_map(value), do: {:ok, value} - def dump(_), do: :error - - def load(value) when is_map(value), do: {:ok, value} - def load(_), do: :error -end diff --git a/lib/stripe_mock_web.ex b/lib/stripe_mock_web.ex index 5e90271..7276f33 100644 --- a/lib/stripe_mock_web.ex +++ b/lib/stripe_mock_web.ex @@ -44,6 +44,7 @@ defmodule StripeMockWeb do import StripeMockWeb.ViewHelpers import StripeMockWeb.Gettext alias StripeMockWeb.Router.Helpers, as: Routes + alias StripeMock.API end end diff --git a/lib/stripe_mock_web/controllers/charge_controller.ex b/lib/stripe_mock_web/controllers/charge_controller.ex index 9de5c85..9fb07a6 100644 --- a/lib/stripe_mock_web/controllers/charge_controller.ex +++ b/lib/stripe_mock_web/controllers/charge_controller.ex @@ -4,8 +4,7 @@ defmodule StripeMockWeb.ChargeController do alias StripeMock.API alias StripeMock.API.Charge - plug SMPlug.ConvertParams, - %{"customer" => "customer_id", "source" => "source_id"} when action in [:create, :update] + plug SMPlug.ConvertParams, %{"customer" => "customer_id"} when action in [:create, :update] action_fallback StripeMockWeb.FallbackController diff --git a/lib/stripe_mock_web/view_helpers.ex b/lib/stripe_mock_web/view_helpers.ex index 5ed8ff7..236c4d5 100644 --- a/lib/stripe_mock_web/view_helpers.ex +++ b/lib/stripe_mock_web/view_helpers.ex @@ -1,7 +1,21 @@ defmodule StripeMockWeb.ViewHelpers do def as_map(struct) do struct + |> update_created() |> Map.delete(:__struct__) |> Map.delete(:__meta__) end + + defp update_created(%{created: %{} = timestamp} = struct) do + created = + timestamp + |> NaiveDateTime.to_erl() + |> :calendar.datetime_to_gregorian_seconds() + + %{struct | created: created} + end + + defp update_created(struct) do + struct + end end diff --git a/lib/stripe_mock_web/views/card_view.ex b/lib/stripe_mock_web/views/card_view.ex index da8578c..dfcfe01 100644 --- a/lib/stripe_mock_web/views/card_view.ex +++ b/lib/stripe_mock_web/views/card_view.ex @@ -15,6 +15,7 @@ defmodule StripeMockWeb.CardView do |> Map.take(~w(id created deleted exp_month exp_year metadata last4 brand)a) |> Map.put("object", "card") |> Map.put("customer", card.customer_id) + |> as_map() end def render("delete.json", %{card: card}) do diff --git a/lib/stripe_mock_web/views/charge_view.ex b/lib/stripe_mock_web/views/charge_view.ex index 92826f3..705ce20 100644 --- a/lib/stripe_mock_web/views/charge_view.ex +++ b/lib/stripe_mock_web/views/charge_view.ex @@ -21,10 +21,11 @@ defmodule StripeMockWeb.ChargeView do metadata: charge.metadata, object: "charge", outcome: render_outcome(charge), - source: render(StripeMockWeb.CardView, "card.json", card: charge.source), + payment_method: render(StripeMockWeb.CardView, "card.json", card: charge.card), statement_descriptor: charge.statement_descriptor, transfer_group: charge.transfer_group } + |> as_map() end defp render_outcome(_charge) do diff --git a/lib/stripe_mock_web/views/source_view.ex b/lib/stripe_mock_web/views/source_view.ex index e0a607b..cb2e5e5 100644 --- a/lib/stripe_mock_web/views/source_view.ex +++ b/lib/stripe_mock_web/views/source_view.ex @@ -2,9 +2,6 @@ defmodule StripeMockWeb.SourceView do use StripeMockWeb, :view def render("show.json", %{source: source}) do - case source.id do - "card_" <> _ -> render_one(source, StripeMockWeb.CardView, "card.json") - _ -> raise "Weird ID" - end + render_one(source, StripeMockWeb.CardView, "card.json") end end diff --git a/lib/stripe_mock_web/views/token_view.ex b/lib/stripe_mock_web/views/token_view.ex index 8df98ce..24cf890 100644 --- a/lib/stripe_mock_web/views/token_view.ex +++ b/lib/stripe_mock_web/views/token_view.ex @@ -26,5 +26,6 @@ defmodule StripeMockWeb.TokenView do else object end + |> as_map() end end diff --git a/mix.exs b/mix.exs index 1e5da9a..025c135 100644 --- a/mix.exs +++ b/mix.exs @@ -10,6 +10,7 @@ defmodule StripeMock.MixProject do compilers: [:phoenix, :gettext] ++ Mix.compilers(), start_permanent: false, description: description(), + aliases: aliases(), package: package(), deps: deps() ] @@ -51,11 +52,18 @@ defmodule StripeMock.MixProject do defp deps do [ {:phoenix, ">= 0.0.0"}, + {:phoenix_ecto, ">= 0.0.0"}, {:gettext, "~> 0.11"}, {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, - {:ecto, "~> 3.1"}, + {:postgrex, ">= 0.0.0"}, + {:ecto, "~> 3.2"}, + {:ecto_sql, "~> 3.2"}, {:ex_doc, ">= 0.0.0", only: :dev} ] end + + defp aliases do + [test: ["ecto.migrate", "test"]] + end end diff --git a/mix.lock b/mix.lock index 10fc0e2..51e8ed2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,12 @@ %{ + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"}, - "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ecto": {:hex, :ecto, "3.2.2", "bb6d1dbcd7ef975b60637e63182e56f3d7d0b5dd9c46d4b9d6183a5c455d65d1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, @@ -12,9 +15,12 @@ "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.6", "8535f4a01291f0fbc2c30c78c4ca6a2eacc148db5178ad76e8b2fc976c590115", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, "plug": {:hex, :plug, "1.8.0", "9d2685cb007fe5e28ed9ac27af2815bc262b7817a00929ac10f56f169f43b977", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug_cowboy": {:hex, :plug_cowboy, "2.0.2", "6055f16868cc4882b24b6e1d63d2bada94fb4978413377a3b32ac16c18dffba2", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, } diff --git a/priv/repo/migrations/20191015221147_create_tables.exs b/priv/repo/migrations/20191015221147_create_tables.exs new file mode 100644 index 0000000..dabfa63 --- /dev/null +++ b/priv/repo/migrations/20191015221147_create_tables.exs @@ -0,0 +1,76 @@ +defmodule StripeMock.Repo.Migrations.CreateTables do + use Ecto.Migration + + defmacro common_fields() do + quote do + add(:deleted, :boolean, null: false, default: false) + add(:description, :string) + add(:metadata, :map, null: false, default: %{}) + timestamps(inserted_at: :created, updated_at: false) + end + end + + def change do + create table(:customers) do + add(:currency, :string, default: "usd") + add(:email, :string) + add(:name, :string) + add(:phone, :string) + + common_fields() + end + + create table(:cards) do + add(:brand, :string) + add(:last4, :string) + add(:source, :string) + + add(:number, :string) + add(:exp_month, :integer) + add(:exp_year, :integer) + add(:cvc, :string) + + add(:customer_id, references(:customers)) + + common_fields() + end + + create table(:tokens) do + add(:client_ip, :string) + add(:type, :string) + add(:used, :boolean, default: false) + + add(:card_id, references(:cards)) + + common_fields() + end + + create table(:sources) do + add(:card_id, references(:cards)) + add(:token_id, references(:tokens)) + end + + create table(:charges) do + add(:amount, :integer) + add(:capture, :boolean, default: false) + add(:currency, :string) + add(:statement_descriptor, :string) + add(:transfer_group, :string) + + add(:customer_id, references(:customers)) + add(:card_id, references(:cards)) + add(:token_id, references(:tokens)) + + common_fields() + end + + create table(:refunds) do + add(:amount, :integer) + add(:reason, :string) + + add(:charge_id, references(:charges)) + + common_fields() + end + end +end diff --git a/test/stripe_mock_web/controllers/charge_controller_test.exs b/test/stripe_mock_web/controllers/charge_controller_test.exs index 7e79b31..eb77903 100644 --- a/test/stripe_mock_web/controllers/charge_controller_test.exs +++ b/test/stripe_mock_web/controllers/charge_controller_test.exs @@ -36,7 +36,7 @@ defmodule StripeMockWeb.ChargeControllerTest do "amount" => 5000, "capture" => true, "currency" => "some currency", - "customer" => "cus_" <> _, + "customer" => _, "description" => "some description", "metadata" => %{}, "statement_descriptor" => "some statement_descriptor", @@ -87,11 +87,6 @@ defmodule StripeMockWeb.ChargeControllerTest do "metadata" => %{"key" => "val"} } = json_response(conn, 200) end - - test "renders errors when data is invalid", %{conn: conn, charge: charge} do - conn = put(conn, Routes.charge_path(conn, :update, charge), invalid_attrs()) - assert json_response(conn, 422)["errors"] != %{} - end end def create_attrs() do diff --git a/test/stripe_mock_web/controllers/refund_controller_test.exs b/test/stripe_mock_web/controllers/refund_controller_test.exs index 835844a..d29daf0 100644 --- a/test/stripe_mock_web/controllers/refund_controller_test.exs +++ b/test/stripe_mock_web/controllers/refund_controller_test.exs @@ -16,21 +16,23 @@ defmodule StripeMockWeb.RefundControllerTest do describe "create refund" do test "renders refund when data is valid", %{conn: conn, charge: charge} do + charge_id = charge.id conn = post(conn, Routes.refund_path(conn, :create), create_attrs(charge.id)) assert %{"id" => id} = json_response(conn, 201) conn = get(conn, Routes.refund_path(conn, :show, id)) assert %{ - "id" => "re_" <> _, + "id" => _, "amount" => 5000, - "charge" => "ch_" <> _, + "charge" => ^charge_id, "created" => _, "metadata" => %{} } = json_response(conn, 200) end test "amount defaults to full amount of the charge", %{conn: conn, charge: charge} do + charge_id = charge.id params = %{create_attrs(charge.id) | amount: nil} conn = post(conn, Routes.refund_path(conn, :create), params) amount = charge.amount @@ -40,9 +42,9 @@ defmodule StripeMockWeb.RefundControllerTest do conn = get(conn, Routes.refund_path(conn, :show, id)) assert %{ - "id" => "re_" <> _, + "id" => _, "amount" => ^amount, - "charge" => "ch_" <> _, + "charge" => ^charge_id, "metadata" => %{} } = json_response(conn, 200) end diff --git a/test/stripe_mock_web/controllers/source_controller_test.exs b/test/stripe_mock_web/controllers/source_controller_test.exs index 1d7f200..8cd8a0f 100644 --- a/test/stripe_mock_web/controllers/source_controller_test.exs +++ b/test/stripe_mock_web/controllers/source_controller_test.exs @@ -40,7 +40,7 @@ defmodule StripeMockWeb.SourceControllerTest do |> post(Routes.customer_source_path(conn, :create, customer.id), source: token) |> json_response(201) - assert id =~ ~r/^card_/ + assert id end test "renders errors when data is invalid", %{conn: conn, customer: customer} do diff --git a/test/stripe_mock_web/controllers/token_controller_test.exs b/test/stripe_mock_web/controllers/token_controller_test.exs index a38802a..ba85aee 100644 --- a/test/stripe_mock_web/controllers/token_controller_test.exs +++ b/test/stripe_mock_web/controllers/token_controller_test.exs @@ -12,13 +12,13 @@ defmodule StripeMockWeb.TokenControllerTest do conn = get(conn, Routes.token_path(conn, :show, id)) assert %{ - "id" => "tok_" <> _, + "id" => _, "client_ip" => "127.0.0.1", "created" => created, "object" => "token", "type" => "card", "used" => false, - "card" => %{"id" => "card_" <> _} + "card" => %{"id" => _} } = json_response(conn, 200) assert is_integer(created) diff --git a/test/test_helper.exs b/test/test_helper.exs index 984ed98..6ba4291 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -37,7 +37,7 @@ defmodule StripeMock.TestHelper do customer_id: customer.id, description: "some description", metadata: %{}, - source_id: token.id, + source: token.id, statement_descriptor: "some statement_descriptor", transfer_group: "some transfer_group" })