Skip to content

Commit

Permalink
Extedn StripeConnectAccountService.update_from_stripe to also create …
Browse files Browse the repository at this point in the history
…associated external account record
  • Loading branch information
begedin committed Jan 17, 2017
1 parent 5ff690f commit 1e90a95
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 155 deletions.
12 changes: 9 additions & 3 deletions lib/code_corps/stripe_service/adapters/stripe_connect_account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,15 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do
map |> add_external_account(stripe_account)
end

defp add_external_account(map, %Stripe.Account{external_accounts: %{data: []}}), do: map
defp add_external_account(map, %Stripe.Account{external_accounts: %{data: [head | _]}}), do: map |> do_add_external_account(head)
defp do_add_external_account(map, %{"id" => id}), do: map |> Map.put(:external_account, id)
defp add_external_account(map, %Stripe.Account{external_accounts: %Stripe.List{data: list}}) do
latest = list |> List.last
map |> do_add_external_account(latest)
end

defp do_add_external_account(map, nil), do: map
defp do_add_external_account(map, %Stripe.ExternalAccount{id: id}) do
map |> Map.put(:external_account, id)
end

defp remove_attributes(%{"legal_entity_verification_status" => "verified"} = attributes) do
attributes |> Map.delete("legal_entity_verification_document")
Expand Down
24 changes: 17 additions & 7 deletions lib/code_corps/stripe_service/adapters/stripe_external_account.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter do
import CodeCorps.MapUtils, only: [rename: 3]

alias CodeCorps.MapUtils
alias CodeCorps.StripeConnectAccount

@stripe_attributes [
:account, :account_holder_name, :account_holder_type, :bank_name, :country,
:account_holder_name, :account_holder_type, :bank_name, :country,
:currency, :default_for_currency, :fingerprint, :id, :last4,
:routing_number, :status
]

def to_params(%Stripe.ExternalAccount{} = bank_account, stripe_connect_account_id \\ nil) do
def to_params(%Stripe.ExternalAccount{} = external_account, %StripeConnectAccount{} = connect_account) do
params =
bank_account
external_account
|> Map.from_struct
|> Map.take(@stripe_attributes)
|> rename(:id, :id_from_stripe)
|> rename(:account, :account_id_from_stripe)
|> Map.put(:stripe_connect_account_id, stripe_connect_account_id)
|> MapUtils.rename(:id, :id_from_stripe)
|> add_association_attributes(connect_account)

{:ok, params}
end

defp add_association_attributes(attributes, %StripeConnectAccount{} = connect_account) do
association_attributes = build_association_attributes(connect_account)
attributes |> Map.merge(association_attributes)
end

defp build_association_attributes(%StripeConnectAccount{id: id, id_from_stripe: id_from_stripe}) do
%{account_id_from_stripe: id_from_stripe, stripe_connect_account_id: id}
end
end
49 changes: 0 additions & 49 deletions lib/code_corps/stripe_service/stripe_connect_account.ex

This file was deleted.

108 changes: 108 additions & 0 deletions lib/code_corps/stripe_service/stripe_connect_account_service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
defmodule CodeCorps.StripeService.StripeConnectAccountService do
alias CodeCorps.{Repo, StripeConnectAccount, StripeExternalAccount}
alias CodeCorps.StripeService.Adapters.{StripeConnectAccountAdapter}
alias CodeCorps.StripeService.StripeConnectExternalAccountService
alias Ecto.Multi

import Ecto.Query, only: [where: 3]

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

@doc """
Used to create a remote `Stripe.Account` record as well as an associated local
`StripeConnectAccount` record.
"""
def create(attributes) do
with {:ok, from_params} <- StripeConnectAccountAdapter.from_params(attributes),
{:ok, %Stripe.Account{} = account} <- @api.Account.create(from_params),
{:ok, params} <- StripeConnectAccountAdapter.to_params(account, attributes)
do
%StripeConnectAccount{} |> StripeConnectAccount.create_changeset(params) |> Repo.insert
else
failure -> failure
end
end

@doc """
Used to update both the local `StripeConnectAccount` as well as the remote `Stripe.Account`,
using attributes sent by the client
"""
def update(%StripeConnectAccount{id_from_stripe: id_from_stripe} = local_account, %{} = attributes) do
with {:ok, from_params} <- StripeConnectAccountAdapter.from_params(attributes),
{:ok, %Stripe.Account{} = api_account} <- @api.Account.update(id_from_stripe, from_params)
do
update_local_account(local_account, api_account, attributes)
end
end

@doc """
Used to update the local `StripeConnectAccount` record using data retrieved from the Stripe API
"""
def update_from_stripe(id_from_stripe) do
with {:ok, %Stripe.Account{} = api_account} <- @api.Account.retrieve(id_from_stripe),
%StripeConnectAccount{} = local_account <- Repo.get_by(StripeConnectAccount, id_from_stripe: id_from_stripe)
do
update_local_account(local_account, api_account)
else
nil -> {:error, :not_found}
failure -> failure
end
end

# updates a StripeConnectAccount record with combined information from the provided
# Stripe.Account record and an optional attributes map
defp update_local_account(
%StripeConnectAccount{} = local_account,
%Stripe.Account{} = api_account,
attributes \\ %{}
) do
with {:ok, params} <- StripeConnectAccountAdapter.to_params(api_account, attributes) do
changeset = local_account |> StripeConnectAccount.webhook_update_changeset(params)

multi = Multi.new
|> Multi.update(:stripe_connect_account, changeset)
|> Multi.run(:process_external_accounts, &process_external_accounts(&1, api_account))

case Repo.transaction(multi) do
{:ok, %{stripe_connect_account: stripe_connect_account, process_external_accounts: _}} ->
{:ok, stripe_connect_account}
{:error, :stripe_connect_account, %Ecto.Changeset{} = changeset, %{}} ->
{:error, changeset}
{:error, failed_operation, failed_value, _changes_so_far} ->
{:error, failed_operation, failed_value}
end
end
end

# goes through all Stripe.ExternalAccount objects within the retrieved Stripe.Account object,
# then either retrieves or creates a StripeExternalAccount object for each of them
defp process_external_accounts(_, %Stripe.Account{external_accounts: %{data: []}}), do: {:ok, []}
defp process_external_accounts(
%{stripe_connect_account: %StripeConnectAccount{} = connect_account},
%Stripe.Account{external_accounts: %{data: new_external_account_list}}
) do
StripeExternalAccount
|> where([e], e.stripe_connect_account_id == ^connect_account.id)
|> Repo.delete_all

new_external_account_list
|> (fn(list) -> [List.last(list)] end).() # We only support one external account for now
|> Enum.map(&find_or_create_external_account(&1, connect_account))
|> Enum.map(&take_record/1)
|> aggregate_records
end

# retrieves or creates a StripeExternalAccount object associated to the provided
# Stripe.ExternalAccount and StripeConnectAccount objects
# returns {:ok, list_of_created_external_account_records}
defp find_or_create_external_account(%Stripe.ExternalAccount{} = api_external_account, connect_account) do
case Repo.get_by(StripeExternalAccount, id_from_stripe: api_external_account.id) do
nil -> StripeConnectExternalAccountService.create(api_external_account, connect_account)
%StripeExternalAccount{} = local_external_account -> {:ok, local_external_account}
end
end

defp take_record({:ok, %StripeExternalAccount{} = external_account}), do: external_account

defp aggregate_records(results), do: {:ok, results}
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,9 @@ defmodule CodeCorps.StripeService.StripeConnectExternalAccountService do

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

def create(id_from_stripe, account_id_from_stripe) do
with {:ok, %Stripe.ExternalAccount{} = external_account} <- @api.ExternalAccount.retrieve(id_from_stripe, connect_account: account_id_from_stripe),
{:ok, %StripeConnectAccount{} = connect_account} <- get_connect_account(account_id_from_stripe),
{:ok, params} <- StripeExternalAccountAdapter.to_params(external_account, connect_account.id)
do
%StripeExternalAccount{}
|> StripeExternalAccount.changeset(params)
|> Repo.insert
else
failure -> failure
end
end

defp get_connect_account(account_id_from_stripe) do
case Repo.get_by(StripeConnectAccount, id_from_stripe: account_id_from_stripe) do
nil -> {:error, :not_found}
record -> {:ok, record}
def create(%Stripe.ExternalAccount{} = external_account, %StripeConnectAccount{} = connect_account) do
with {:ok, params} <- StripeExternalAccountAdapter.to_params(external_account, connect_account) do
%StripeExternalAccount{} |> StripeExternalAccount.changeset(params) |> Repo.insert
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ defmodule CodeCorps.StripeService.WebhookProcessing.ConnectEventHandler do
def handle_event(%{type: type} = attributes), do: do_handle(type, attributes)

defp do_handle("account.updated", attributes), do: Events.AccountUpdated.handle(attributes)
defp do_handle("account.external_account.created", attributes), do: Events.ConnectExternalAccountCreated.handle(attributes)
defp do_handle("customer.subscription.deleted", attributes), do: Events.CustomerSubscriptionDeleted.handle(attributes)
defp do_handle("customer.subscription.updated", attributes), do: Events.CustomerSubscriptionUpdated.handle(attributes)
defp do_handle("invoice.payment_succeeded", attributes), do: Events.InvoicePaymentSucceeded.handle(attributes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.IgnoredEventHandler do
alias CodeCorps.{StripeEvent, Repo}

@ignored_event_types [
"account.external_account.created",
"application_fee.created",
"customer.created",
"customer.source.created",
Expand Down Expand Up @@ -39,6 +40,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.IgnoredEventHandler do
end

@spec get_reason(String.t) :: String.t
defp get_reason("account.external_account.created"), do: "External accounts are stored locally upon updating a connect account."
defp get_reason("application_fee.created"), do: "We don't make use of the application fee object."
defp get_reason("customer.created"), do: "Customers are only created from the client."
defp get_reason("customer.source.created"), do: "Cards are only created from the client. No need to handle"
Expand Down
8 changes: 7 additions & 1 deletion lib/code_corps/stripe_testing/account.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
defmodule CodeCorps.StripeTesting.Account do
import CodeCorps.StripeTesting.Helpers

def create(attributes) do
{:ok, create_stripe_record(attributes)}
end

def retrieve("account_with_multiple_external_accounts") do
{:ok, load_fixture(Stripe.Account, "account_with_multiple_external_accounts")}
end

def retrieve(id) do
{:ok, create_stripe_record(%{"id" => id})}
end
Expand Down Expand Up @@ -60,7 +66,7 @@ defmodule CodeCorps.StripeTesting.Account do
defp add_external_account(%{"id" => account_id, "external_account" => external_account_id} = map) do
external_accounts_map = %{
"object" => "list",
"data" => [%{"id" => external_account_id}],
"data" => [%{"id" => external_account_id, "object" => "bank_account"}],
"has_more" => false,
"total_count" => 1,
"url" => "/v1/accounts/#{account_id}/external_accounts"
Expand Down
Loading

0 comments on commit 1e90a95

Please sign in to comment.