Skip to content

Commit

Permalink
Merge pull request #508 from code-corps/475-handle-customer-source-up…
Browse files Browse the repository at this point in the history
…dated

Handle customer.source.updated, propagate platform card updates to connect card
  • Loading branch information
joshsmith committed Dec 6, 2016
2 parents 944e92e + de690f9 commit b2312c7
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule CodeCorps.StripeService.Events.CustomerSourceUpdated do
def handle(%{"data" => %{"object" => %{"id" => card_id, "object" => "card"}}}) do
CodeCorps.StripeService.StripePlatformCardService.update_from_stripe(card_id)
end

def handle(%{"data" => %{"object" => %{"id" => _, "object" => _}}}), do: {:error, :unsupported_object}
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule CodeCorps.StripeService.Events.CustomerSubscriptionDeleted do
@api Application.get_env(:code_corps, :stripe)

def handle(%{"data" => %{"object" => %{"id" => stripe_sub_id, "customer" => connect_customer_id}}}) do
CodeCorps.StripeService.StripeConnectSubscriptionService.update_from_stripe(stripe_sub_id, connect_customer_id)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule CodeCorps.StripeService.Events.CustomerSubscriptionUpdated do
@api Application.get_env(:code_corps, :stripe)

def handle(%{"data" => %{"object" => %{"id" => stripe_sub_id, "customer" => connect_customer_id}}}) do
CodeCorps.StripeService.StripeConnectSubscriptionService.update_from_stripe(stripe_sub_id, connect_customer_id)
end
Expand Down
19 changes: 19 additions & 0 deletions lib/code_corps/stripe_service/stripe_connect_card.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ defmodule CodeCorps.StripeService.StripeConnectCardService do
end
end

def update(%StripeConnectCard{} = record, %{} = attributes) do
with {:ok, %Stripe.Card{} = updated_stripe_card} <- update_on_stripe(record, attributes)
do
{:ok, updated_stripe_card}
else
{:error, %Stripe.APIErrorResponse{} = error} -> {:error, error}
_ -> {:error, :unhandled}
end
end

defp create(%StripePlatformCard{} = platform_card, %StripeConnectCustomer{} = connect_customer, %StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account) do
platform_customer_id = platform_customer.id_from_stripe
platform_card_id = platform_card.id_from_stripe
Expand Down Expand Up @@ -55,4 +65,13 @@ defmodule CodeCorps.StripeService.StripeConnectCardService do
|> Map.put(:stripe_connect_account_id, connect_account.id)
|> keys_to_string
end

defp update_on_stripe(%StripeConnectCard{} = record, attributes) do
@api.Card.update(
:customer,
record.stripe_platform_card.customer_id_from_stripe,
record.id_from_stripe,
attributes,
connect_account: record.stripe_connect_account.id_from_stripe)
end
end
4 changes: 2 additions & 2 deletions lib/code_corps/stripe_service/stripe_connect_customer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ defmodule CodeCorps.StripeService.StripeConnectCustomerService do
end
end

def update(%StripeConnectCustomer{stripe_connect_account: connect_account} = connect_customer, attributes) do
@api.Customer.update(connect_customer.id_from_stripe, attributes, connect_account: connect_account.id_from_stripe)
def update(%StripeConnectCustomer{id_from_stripe: id_from_stripe, stripe_connect_account: connect_account} = connect_customer, attributes) do
@api.Customer.update(id_from_stripe, attributes, connect_account: connect_account.id_from_stripe)
end

defp create(%StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account) do
Expand Down
78 changes: 73 additions & 5 deletions lib/code_corps/stripe_service/stripe_platform_card.ex
Original file line number Diff line number Diff line change
@@ -1,27 +1,95 @@
defmodule CodeCorps.StripeService.StripePlatformCardService do
alias CodeCorps.Repo
alias CodeCorps.StripeService.Adapters.StripePlatformCardAdapter
alias CodeCorps.StripePlatformCard
alias CodeCorps.StripePlatformCustomer
alias CodeCorps.StripeService.StripeConnectCardService
alias CodeCorps.{StripeConnectCard, StripePlatformCard, StripePlatformCustomer}

alias Ecto.Multi

@api Application.get_env(:code_corps, :stripe)

def create(%{"stripe_token" => stripe_token, "user_id" => user_id} = attributes) do
with %StripePlatformCustomer{} = customer <- get_customer(user_id),
{:ok, card} <- @api.Card.create(:customer, customer.id_from_stripe, stripe_token),
{:ok, params} <- StripePlatformCardAdapter.to_params(card, attributes)
{:ok, card} <- @api.Card.create(:customer, customer.id_from_stripe, stripe_token),
{:ok, params} <- StripePlatformCardAdapter.to_params(card, attributes)
do
%StripePlatformCard{}
|> StripePlatformCard.create_changeset(params)
|> Repo.insert
else
{:error, error} -> {:error, error}
nil -> {:error, :not_found}
{:error, %Stripe.APIErrorResponse{} = error} -> {:error, error}
{:error, %Ecto.Changeset{} = changeset} -> {:error, changeset}
end
end

def update_from_stripe(card_id) do
with {:ok, %StripePlatformCard{} = record} <- get_card(card_id),
{:ok, %Stripe.Card{} = stripe_card} <- get_card_from_stripe(record),
{:ok, params} <- StripePlatformCardAdapter.to_params(stripe_card, %{})
do
perform_update(record, params)
else
nil -> {:error, :not_found}
{:error, %Stripe.APIErrorResponse{} = error} -> {:error, error}
{:error, %Ecto.Changeset{} = changeset} -> {:error, changeset}
end
end

defp get_customer(user_id) do
StripePlatformCustomer
|> CodeCorps.Repo.get_by(user_id: user_id)
end

defp get_card(card_id_from_stripe) do
record = Repo.get_by(StripePlatformCard, id_from_stripe: card_id_from_stripe)
{:ok, record}
end

defp get_card_from_stripe(%StripePlatformCard{id_from_stripe: stripe_id, customer_id_from_stripe: owner_id}) do
@api.Card.retrieve(:customer, owner_id, stripe_id)
end

defp perform_update(record, params) do
changeset = record |> StripePlatformCard.update_changeset(params)

multi =
Multi.new
|> Multi.update(:update_platform_card, changeset)
|> Multi.run(:update_connect_cards, &update_connect_cards/1)

case Repo.transaction(multi) do
{:ok, %{update_platform_card: platform_card_update, update_connect_cards: connect_card_updates}} ->
{:ok, platform_card_update, connect_card_updates}
{:error, :update_platform_card, %Ecto.Changeset{} = changeset, %{}} ->
{:error, changeset}
{:error, _failed_operation, _failed_value, _changes_so_far} ->
{:error, :unhandled}
end
end

defp update_connect_cards(%{update_platform_card: %StripePlatformCard{} = stripe_platform_card}) do
attributes = connect_card_attributes(stripe_platform_card)

case do_update_connect_cards(stripe_platform_card, attributes) do
[_h | _t] = results -> {:ok, results}
[] -> {:ok, nil}
end
end

defp connect_card_attributes(stripe_platform_card) do
stripe_platform_card |> Map.take([:exp_month, :exp_year, :name])
end

defp do_update_connect_cards(stripe_platform_card, attributes) when attributes == %{}, do: []
defp do_update_connect_cards(stripe_platform_card, attributes) do
stripe_platform_card
|> Repo.preload([stripe_connect_cards: [:stripe_connect_account, :stripe_platform_card]])
|> Map.get(:stripe_connect_cards)
|> Enum.map(&do_update_connect_card(&1, attributes))
end

defp do_update_connect_card(%StripeConnectCard{} = stripe_connect_card, attributes) do
stripe_connect_card |> StripeConnectCardService.update(attributes)
end
end
68 changes: 64 additions & 4 deletions lib/code_corps/stripe_testing/card.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
defmodule CodeCorps.StripeTesting.Card do
def create(:customer, _stripe_id, _stripe_token, _opts \\ []) do
{:ok, do_create}
def create(:customer, stripe_id, _stripe_token, _opts \\ []) do
{:ok, do_create(stripe_id)}
end

defp do_create do
defp do_create(stripe_id) do
%Stripe.Card{
id: "card_19IHPnBKl1F6IRFf8w7gpdOe",
id: stripe_id,
address_city: nil,
address_country: nil,
address_line1: nil,
Expand All @@ -28,4 +28,64 @@ defmodule CodeCorps.StripeTesting.Card do
tokenization_method: nil
}
end

def retrieve(:customer, owner_id, stripe_id, _opts \\ []) do
{:ok, do_retrieve(owner_id, stripe_id)}
end

defp do_retrieve(owner_id, stripe_id) do
%Stripe.Card{
id: stripe_id,
address_city: nil,
address_country: nil,
address_line1: nil,
address_line1_check: nil,
address_line2: nil,
address_state: nil,
address_zip: nil,
address_zip_check: nil,
brand: "Visa",
country: "US",
customer: owner_id,
cvc_check: "unchecked",
dynamic_last4: nil,
exp_month: 12,
exp_year: 2020,
funding: "credit",
last4: "4242",
metadata: {},
name: "John Doe",
tokenization_method: nil
}
end

def update(:customer, owner_id, stripe_id, attributes, _opts \\ []) do
{:ok, do_update(owner_id, stripe_id, attributes)}
end

defp do_update(owner_id, stripe_id, %{name: name, exp_month: exp_month, exp_year: exp_year}) do
%Stripe.Card{
id: stripe_id,
address_city: nil,
address_country: nil,
address_line1: nil,
address_line1_check: nil,
address_line2: nil,
address_state: nil,
address_zip: nil,
address_zip_check: nil,
brand: "Visa",
country: "US",
customer: owner_id,
cvc_check: "unchecked",
dynamic_last4: nil,
exp_month: exp_month,
exp_year: exp_year,
funding: "credit",
last4: "4242",
metadata: {},
name: name,
tokenization_method: nil
}
end
end
26 changes: 25 additions & 1 deletion test/controllers/stripe_platform_events_controller_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule CodeCorps.StripePlatformEventsControllerTest do
use CodeCorps.ConnCase

alias CodeCorps.StripePlatformCustomer
alias CodeCorps.{StripePlatformCard,StripePlatformCustomer}

setup do
conn =
Expand All @@ -12,6 +12,12 @@ defmodule CodeCorps.StripePlatformEventsControllerTest do
{:ok, conn: conn}
end

@card %{
"id" => "card_19LEnDBKl1F6IRFfjLfJRYuN",
"object" => "card",
"customer" => "cus_9e9KNE2beHhfLy"
}

defp event_for(object, type) do
%{
"api_version" => "2016-07-06",
Expand Down Expand Up @@ -56,4 +62,22 @@ defmodule CodeCorps.StripePlatformEventsControllerTest do
assert platform_customer.email == "hardcoded@test.com"
end
end

describe "customer.source.updated" do
test "returns 200 and updates card when one matches", %{conn: conn} do
event = event_for(@card, "customer.source.updated")
stripe_id = @card["id"]
platform_customer_id = @card["customer"]

insert(:stripe_platform_customer, id_from_stripe: platform_customer_id)
platform_card = insert(:stripe_platform_card, id_from_stripe: stripe_id, customer_id_from_stripe: platform_customer_id)

path = stripe_platform_events_path(conn, :create)
assert conn |> post(path, event) |> response(200)

updated_card = Repo.get_by(StripePlatformCard, id: platform_card.id)
# hardcoded in StripeTesting.Card
assert updated_card.name == "John Doe"
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule CodeCorps.StripeService.StripeConnectCardServiceTest do
use ExUnit.Case, async: true

use CodeCorps.ModelCase

alias CodeCorps.StripeConnectCard
alias CodeCorps.StripeService.StripeConnectCardService

describe "update/1" do
@attributes %{name: "John Doe", exp_month: 6, exp_year: 2030}

test "it just updates the connect card on Stripe API, not locally" do
connect_card = insert(:stripe_connect_card)

connect_card =
StripeConnectCard
|> Repo.get(connect_card.id)
|> Repo.preload([:stripe_platform_card, :stripe_connect_account])

updated_at = connect_card.updated_at

{:ok, %Stripe.Card{} = stripe_card} =
StripeConnectCardService.update(connect_card, @attributes)

assert stripe_card.id == connect_card.id_from_stripe
assert stripe_card.name == "John Doe"
assert stripe_card.exp_year == 2030
assert stripe_card.exp_month == 6

connect_card = Repo.get(StripeConnectCard, connect_card.id)

assert connect_card.updated_at == updated_at
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule CodeCorps.StripeService.StripePlatformCardServiceTest do
use ExUnit.Case, async: true

use CodeCorps.ModelCase

alias CodeCorps.StripePlatformCard
alias CodeCorps.StripeService.StripePlatformCardService

describe "update_from_stripe/1" do
test "it just updates the platform card if there is nothing associated to update" do
platform_card = insert(:stripe_platform_card)

{:ok, %StripePlatformCard{} = platform_card, nil} =
StripePlatformCardService.update_from_stripe(platform_card.id_from_stripe)

assert platform_card.exp_year == 2020
end

# TODO: We can't really do this test until we are able to mock stripe API data
# test "it returns an {:error, changeset} if there are validation errors with the platform_card" do
# platform_card = insert(:stripe_platform_card)

# {:error, changeset} =
# StripePlatformCardService.update_from_stripe(platform_card.id_from_stripe)

# refute changeset.valid?
# end

test "it also updates the associated connect cards if there are any" do
platform_card = insert(:stripe_platform_card)

[connect_card_1, connect_card_2] = insert_pair(:stripe_connect_card, stripe_platform_card: platform_card)

{:ok, %StripePlatformCard{} = platform_card, connect_updates} =
StripePlatformCardService.update_from_stripe(platform_card.id_from_stripe)

assert platform_card.exp_year == 2020

platform_card = Repo.get(StripePlatformCard, platform_card.id)
assert platform_card.exp_year == 2020

[
{:ok, %Stripe.Card{} = stripe_record_1},
{:ok, %Stripe.Card{} = stripe_record_2}
] = connect_updates

assert stripe_record_1.id == connect_card_1.id_from_stripe
assert stripe_record_1.exp_year == 2020
assert stripe_record_2.id == connect_card_2.id_from_stripe
assert stripe_record_2.exp_year == 2020
end
end
end
Loading

0 comments on commit b2312c7

Please sign in to comment.