Skip to content
Permalink
Browse files

Merge pull request #24 from ryal/credit-cards-and-stripe

Reorganize Payment Gateway Structure and Fix Payment Method Creation
  • Loading branch information...
dhonig committed Apr 19, 2017
2 parents d717854 + 63f71df commit 5dde38ab6769cc9e3b1d3717fa0904d615ab3d7c
@@ -53,7 +53,9 @@ config :ryal_core,
user_module: App.User,
user_table: :users,
default_payment_gateway: :bogus,
fallback_payment_gateways: [:stripe, :braintree]
payment_gateways: %{
stripe: "sk_test_123"
}
```

Now you'll want to copy over the migrations.
@@ -3,15 +3,22 @@ defmodule Ryal do
The core Ryal namespace. This guy is primarily used for configuration.
"""

@payment_gateway Application.get_env(:ryal_core, :payment_gateway)
@repo Application.get_env(:ryal_core, :repo)
@user_module Application.get_env(:ryal_core, :user_module)
@user_table Application.get_env(:ryal_core, :user_table)

use Application

def payment_gateway, do: @payment_gateway
def payment_gateway_keys, do: Map.get(@payment_gateway, :keys) || %{}
import Application, only: [get_env: 2]

@default_payment_gateway get_env(:ryal_core, :default_payment_gateway)
@payment_gateways get_env(:ryal_core, :payment_gateways)

@repo get_env(:ryal_core, :repo)
@user_module get_env(:ryal_core, :user_module)
@user_table get_env(:ryal_core, :user_table)

def payment_gateways, do: @payment_gateways
def default_payment_gateway, do: @default_payment_gateway
def fallback_gateways do
Map.keys(payment_gateways() || %{}) -- [default_payment_gateway()]
end

def repo, do: @repo
def user_module, do: @user_module
@@ -5,17 +5,16 @@ defmodule Ryal.PaymentGatewayCommand do
"""

alias Ryal.PaymentGateway
alias Ryal.PaymentGateway.Customer

@default_gateway Map.get(Ryal.payment_gateway, :default)
@fallback_gateways Map.get(Ryal.payment_gateway, :fallbacks)

@doc "Shorthand for creating all the payment gateways relevant to a user."
@spec create(Ecto.Schema.t) ::
{:ok, Ecto.Schema.t} | {:error, Ecto.Changeset.t}
def create(user) do
Enum.each @fallback_gateways || [], fn(gateway_type) ->
Enum.each Ryal.fallback_gateways() || [], fn(gateway_type) ->
spawn_monitor fn -> create(gateway_type, user) end
end

create @default_gateway, user
create Ryal.default_payment_gateway(), user
end

@doc """
@@ -27,7 +26,7 @@ defmodule Ryal.PaymentGatewayCommand do
def create(type, user) do
struct = %PaymentGateway{type: Atom.to_string(type), user_id: user.id}

with {:ok, external_id} <- Customer.create(type, user),
with {:ok, external_id} <- payment_gateway(type).create(:customer, user),
changeset <-
PaymentGateway.changeset(%{struct | external_id: external_id}),
do: Ryal.repo.insert(changeset)
@@ -52,7 +51,17 @@ defmodule Ryal.PaymentGatewayCommand do

Enum.map user.payment_gateways, fn(payment_gateway) ->
type = String.to_atom payment_gateway.type
spawn_monitor Customer, action, [type, payment_gateway]
spawn_monitor payment_gateway(type), action, [:customer, payment_gateway]
end
end

defp payment_gateway(type) do
module_type = type
|> Atom.to_string
|> Macro.camelize

{module, []} = Code.eval_string("Ryal.PaymentGateway.#{module_type}")

module
end
end
@@ -0,0 +1,25 @@
defmodule Ryal.PaymentGateway.Bogus do
@moduledoc """
A very simple payment gateway module that provides the basic functions to fake
create, update, and delete methods with a payment gateway.
"""

@doc "Simple bogus create function for an external_id."
@spec create(atom, Ecto.Schema.t) :: {:ok, String.t}
def create(_atom, _schema), do: {:ok, random_id()}

@doc "Simple bogus update function."
@spec update(atom, Ecto.Schema.t) :: {:ok, %{}}
def update(_atom, _schema), do: {:ok, %{}}

@doc "Simple bogus delete function."
@spec delete(atom, Ecto.Schema.t) :: {:ok, %{}}
def delete(_atom, _schema), do: {:ok, %{}}

defp random_id do
:rand.uniform * 10_000_000_000
|> round
|> to_string
|> String.ljust(10, ?0)
end
end

This file was deleted.

Oops, something went wrong.
@@ -0,0 +1,76 @@
defmodule Ryal.PaymentGateway.Stripe do
@moduledoc "Relevant functions for working with the Stipe API."

alias Ryal.PaymentGatewayQuery

@stripe_api_key Map.get(Ryal.payment_gateways(), :stripe)
@stripe_base "https://#{@stripe_api_key}:@api.stripe.com"

@spec create(atom, Ecto.Schema.t, String.t) :: {:ok, String.t}
def create(type, schema, stripe_base \\ @stripe_base)

def create(:credit_card, payment_method, stripe_base) do
credit_card_data = payment_method.proxy.data
customer_id = payment_method.user_id
|> PaymentGatewayQuery.get_external_id("stripe")
|> Ryal.repo.one!

credit_card_path = "/v1/customers/#{customer_id}/sources"
create_object credit_card_data, :credit_card, credit_card_path, stripe_base
end

def create(:customer, user, stripe_base) do
create_object user, :customer, "/v1/customers", stripe_base
end

defp create_object(schema, type, path, stripe_base) do
response = HTTPotion.post(stripe_base <> path, [body: params(type, schema)])

with {:ok, body} <- Poison.decode(response.body),
do: {:ok, body["id"]}
end

@spec update(atom, Ecto.Schema.t, String.t) :: {:ok, %{}}
def update(type, schema, stripe_base \\ @stripe_base)

@doc "Updates information on Stripe when the user data changes."
def update(:customer, payment_gateway, stripe_base) do
user = payment_gateway.user

response = stripe_base
<> "/v1/customers/#{payment_gateway.external_id}"
|> HTTPotion.post([body: params(:customer, user)])

Poison.decode(response.body)
end

@spec delete(atom, Ecto.Schema.t, String.t) :: {:ok, %{}}
def delete(atom, schema, stripe_base \\ @stripe_base)

@doc "Marks a customer account on Stripe as deleted."
def delete(:customer, payment_gateway, stripe_base) do
response = stripe_base
<> "/v1/customers/#{payment_gateway.external_id}"
|> HTTPotion.delete

Poison.decode(response.body)
end

defp params(:credit_card, credit_card) do
URI.encode_query %{
"source[object]" => "card",
"source[exp_month]" => credit_card.month,
"source[exp_year]" => credit_card.year,
"source[number]" => credit_card.number,
"source[cvc]" => credit_card.cvc,
"source[name]" => credit_card.name
}
end

defp params(:customer, user) do
URI.encode_query %{
email: user.email,
description: "Customer #{user.id} from Ryal."
}
end
end
@@ -37,11 +37,13 @@ defmodule Ryal.PaymentMethod do
schema "ryal_payment_methods" do
field :type, :string

embeds_one :data, Ryal.PaymentMethod.Proxy
embeds_one :proxy, Ryal.PaymentMethod.Proxy

has_many :payment_method_gateways, Ryal.PaymentMethodGateway

belongs_to :user, Ryal.user_module()

timestamps()
end

@required_fields ~w(type user_id)a
@@ -58,15 +60,15 @@ defmodule Ryal.PaymentMethod do
|> cast(set_module_type(params), @required_fields)
|> validate_required(@required_fields)
|> validate_inclusion(:type, @payment_method_types)
|> cast_embed(:data, required: true)
|> cast_embed(:proxy, required: true)
end

defp set_module_type(%{type: type} = params)
when type in @payment_method_types do
module_type = Macro.camelize(type)
{module_name, []} = Code.eval_string("Ryal.PaymentMethod.#{module_type}")
data = Map.get(params, :data, %{})
Map.put(params, :data, struct(module_name, data))
proxy_data = Map.get(params, :proxy, %{})
Map.put(params, :proxy, struct(module_name, proxy_data))
end

defp set_module_type(params), do: params
@@ -10,6 +10,7 @@ defmodule Ryal.PaymentMethod.Proxy do
import Ecto.Changeset, only: [cast: 3]

embedded_schema do
field :data, :map
end

@doc """
@@ -38,8 +39,15 @@ defmodule Ryal.PaymentMethod.Proxy do
%module{} = struct
params = Map.from_struct(struct)

module
|> struct(%{})
|> module.changeset(params)
proxy_changeset = module
|> struct(%{})
|> module.changeset(params)

%__MODULE__{}
|> cast(%{data: params}, [:data])
|> Map.merge(%{
errors: proxy_changeset.errors,
valid?: proxy_changeset.valid?
})
end
end
@@ -0,0 +1,18 @@
defmodule Ryal.PaymentGatewayQuery do
@moduledoc "Queries for the `Ryal.PaymentGateway`."

use Ryal.Web, :query

alias Ryal.PaymentGateway

@doc """
Have a user id and want to get its external_id to one of the payment
gateways? This query should help you out fine.
"""
@spec get_external_id(integer, String.t) :: Ecto.Query.t
def get_external_id(user_id, gateway_type) do
from pg in PaymentGateway,
where: [user_id: ^user_id, type: ^gateway_type],
select: pg.external_id
end
end
@@ -3,7 +3,8 @@ defmodule Ryal.Repo.Migrations.CreateRyalPaymentMethods do

def change do
create table(:ryal_payment_methods) do
add :data, :map, null: false
add :type, :string, null: false
add :proxy, :map, null: false

add :user_id, references(Ryal.user_table()), null: false

@@ -0,0 +1,24 @@
{
"id": "card_1AA3En2BZSQJcNSQ77orWzVS",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_AUIXSS9KUHRv5H",
"cvc_check": null,
"dynamic_last4": null,
"exp_month": 8,
"exp_year": 2018,
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
}
Oops, something went wrong.

0 comments on commit 5dde38a

Please sign in to comment.
You can’t perform that action at this time.