Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter do
:routing_number, :status
]

def to_params(%Stripe.ExternalAccount{} = bank_account) do
def to_params(%Stripe.ExternalAccount{} = bank_account, stripe_connect_account_id \\ nil) do
params =
bank_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)

{:ok, params}
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
defmodule CodeCorps.StripeService.StripeConnectExternalAccountService do
alias CodeCorps.{Repo, StripeExternalAccount}
alias CodeCorps.{Repo, StripeConnectAccount, StripeExternalAccount}
alias CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter

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

def create(id_from_stripe, account_id_from_stripe) do
with {:ok, %Stripe.ExternalAccount{} = bank_account} <- @api.ExternalAccount.retrieve(id_from_stripe, connect_account: account_id_from_stripe),
{:ok, params} <- StripeExternalAccountAdapter.to_params(bank_account)
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
end
end

defp get_connect_account(account_id_from_stripe) do
Copy link
Contributor

@begedin begedin Jan 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason we do this at the service instead of the adapter level is because we want the service to return a useful error response in case the connect account was not found.

Specifically, the service will return {:ok, created_external_account} if everything is ok, and {:error, :not_found} if the associated connect account was not found locally.

Due to this behavior, the event handling system will mark the event as errored.

We could technically push it further into the adapter, but the code would be more complicated.

case Repo.get_by(StripeConnectAccount, id_from_stripe: account_id_from_stripe) do
nil -> {:error, :not_found}
record -> {:ok, record}
end
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"segment": {:hex, :segment, "0.1.1", "47bf9191590e7a533c105d1e21518e0d6da47c91e8d98ebb649c624db5dfc359", [:mix], [{:httpoison, "~> 0.8", [hex: :httpoison, optional: false]}, {:poison, "~> 1.3 or ~> 2.0", [hex: :poison, optional: false]}]},
"sentry": {:hex, :sentry, "2.1.0", "51e7ca261b519294ac73b30763893c4a7ad2005205514aefa5bf37ccb83e44ea", [:mix], [{:hackney, "~> 1.6.1", [hex: :hackney, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, optional: false]}]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:make, :rebar], []},
"stripity_stripe": {:git, "https://github.com/code-corps/stripity_stripe.git", "f325778c8fb2af3bc04c9d894daef0c47c1500de", [branch: "2.0"]},
"stripity_stripe": {:git, "https://github.com/code-corps/stripity_stripe.git", "f6ddf082a86f558e23789f24aea581add3ca8821", [branch: "2.0"]},
"sweet_xml": {:hex, :sweet_xml, "0.6.3", "814265792baeb163421811c546581c522dfdcb9d1767b1e59959c52906414e80", [:mix], []},
"timber": {:hex, :timber, "0.4.7", "df3fcd79bcb4eb4b53874d906ef5f3a212937b4bc7b7c5b244745202cc389443", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: true]}, {:phoenix, "~> 1.2", [hex: :phoenix, optional: true]}, {:plug, "~> 1.2", [hex: :plug, optional: true]}, {:poison, "~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"timex": {:hex, :timex, "3.1.5", "413d6d8d6f0162a5d47080cb8ca520d790184ac43e097c95191c7563bf25b428", [:mix], [{:combine, "~> 0.7", [hex: :combine, optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, optional: false]}]},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule CodeCorps.Repo.Migrations.AddStripeConnectAccountReferenceToExternalAccounts do
use Ecto.Migration

def change do
alter table(:stripe_external_accounts) do
add :stripe_connect_account_id, references(:stripe_connect_accounts)
end
end
end
17 changes: 17 additions & 0 deletions test/controllers/stripe_connect_events_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ defmodule CodeCorps.StripeConnectEventsControllerTest do
event = event_for(@bank_account, "account.external_account.created")
path = stripe_connect_events_path(conn, :create)

# we expect the event to be associated with an account, so it must be created
insert(:stripe_connect_account, id_from_stripe: @bank_account["account"])

assert conn |> post(path, event) |> response(200)

wait_for_supervisor
Expand All @@ -157,6 +160,20 @@ defmodule CodeCorps.StripeConnectEventsControllerTest do
created_account = Repo.one(StripeExternalAccount)
assert created_account
end

test "errors out event if no associated connect account", %{conn: conn} do
event = event_for(@bank_account, "account.external_account.created")
path = stripe_connect_events_path(conn, :create)

assert conn |> post(path, event) |> response(200)

wait_for_supervisor

event = Repo.one(StripeEvent)
assert event.status == "errored"

assert [] == Repo.all(StripeExternalAccount)
end
end

describe "any event" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountTestAdapter do

import CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter, only: [to_params: 1]

@stripe_connect_account %Stripe.ExternalAccount{
@stripe_external_account %Stripe.ExternalAccount{
id: "ba_19SSZG2eZvKYlo2CXnmzYU5H",
object: "bank_account",
account: "acct_1032D82eZvKYlo2C",
Expand Down Expand Up @@ -32,12 +32,13 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountTestAdapter do
fingerprint: "1JWtPxqbdX5Gamtc",
last4: "6789",
routing_number: "110000000",
status: "new"
status: "new",
stripe_connect_account_id: nil
}

describe "to_params/2" do
test "converts from stripe map to local properly" do
{:ok, result} = to_params(@stripe_connect_account)
{:ok, result} = to_params(@stripe_external_account)
assert result == @local_map
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ defmodule CodeCorps.StripeService.StripeConnectExternalAccountServiceTest do
id_from_stripe = "ba_testing123"
account_id_from_stripe = "acct_123"

{:ok, %CodeCorps.StripeExternalAccount{} = bank_account} =
connect_account = insert(:stripe_connect_account, id_from_stripe: account_id_from_stripe)

{:ok, %CodeCorps.StripeExternalAccount{} = external_account} =
StripeConnectExternalAccountService.create(id_from_stripe, account_id_from_stripe)

assert bank_account.id_from_stripe == id_from_stripe
assert external_account.id_from_stripe == id_from_stripe
assert external_account.stripe_connect_account_id == connect_account.id
end

test "returns {:error, :not_found} if there's no associated stripe connect account" do
id_from_stripe = "ba_testing123"
account_id_from_stripe = "acct_123"

assert {:error, :not_found} == StripeConnectExternalAccountService.create(id_from_stripe, account_id_from_stripe)
end
end
end
9 changes: 8 additions & 1 deletion test/support/factories.ex
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,20 @@ defmodule CodeCorps.Factories do

def stripe_event_factory do
%CodeCorps.StripeEvent{
endpoint: sequence(:endpoint, fn(_) -> Enum.random(~w{ connect platform }) end),
endpoint: sequence(:endpoint, fn(_) -> Enum.random(~w{ connect platform }) end),
id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"),
status: sequence(:status, fn(_) -> Enum.random(~w{ unprocessed processed errored }) end),
type: "test.type"
}
end

def stripe_external_account_factory do
%CodeCorps.StripeExternalAccount{
account_id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"),
id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}")
}
end

def stripe_file_upload_factory do
%CodeCorps.StripeFileUpload{
id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"),
Expand Down
21 changes: 21 additions & 0 deletions test/views/stripe_connect_account_view_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ defmodule CodeCorps.StripeConnectAccountViewTest do
verification_disabled_reason: "fields_needed",
verification_fields_needed: ["legal_entity.first_name", "legal_entity.last_name"]
)
insert(:stripe_external_account,
stripe_connect_account: account,
bank_name: "Wells Fargo",
last4: "1234",
routing_number: "123456789"
)

rendered_json = render(CodeCorps.StripeConnectAccountView, "show.json-api", data: account)

expected_json = %{
"data" => %{
"attributes" => %{
"bank-account-bank-name" => "Wells Fargo",
"bank-account-last4" => "1234",
"bank-account-routing-number" => "123456789",
"bank-account-status" => "pending_requirement",
"business-name" => account.business_name,
"business-url" => account.business_url,
Expand Down Expand Up @@ -308,4 +317,16 @@ defmodule CodeCorps.StripeConnectAccountViewTest do
assert rendered_json["data"]["attributes"]["bank-account-status"] == "verified"
end
end

describe "external account fields" do
test "render if there is an associated external account" do
account = insert(:stripe_connect_account)
insert(:stripe_external_account, last4: "ABCD", routing_number: "123456", stripe_connect_account: account)

rendered_json = render(CodeCorps.StripeConnectAccountView, "show.json-api", data: account)

assert rendered_json["data"]["attributes"]["bank-account-last4"] == "ABCD"
assert rendered_json["data"]["attributes"]["bank-account-routing-number"] == "123456"
end
end
end
1 change: 1 addition & 0 deletions web/models/stripe_connect_account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ defmodule CodeCorps.StripeConnectAccount do
field :verification_fields_needed, {:array, :string}, default: []

belongs_to :organization, CodeCorps.Organization
has_one :stripe_external_account, CodeCorps.StripeExternalAccount

timestamps()
end
Expand Down
6 changes: 5 additions & 1 deletion web/models/stripe_external_account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ defmodule CodeCorps.StripeExternalAccount do
field :routing_number, :string
field :status, :string

belongs_to :stripe_connect_account, CodeCorps.StripeConnectAccount

timestamps()
end

@create_params [
:id_from_stripe, :account_id_from_stripe, :account_holder_name, :account_holder_type, :bank_name,
:country, :currency, :default_for_currency, :fingerprint, :last4, :routing_number, :status
:country, :currency, :default_for_currency, :fingerprint, :last4, :routing_number, :status,
:stripe_connect_account_id
]

@required_create_params [:id_from_stripe, :account_id_from_stripe]
Expand All @@ -32,5 +35,6 @@ defmodule CodeCorps.StripeExternalAccount do
struct
|> cast(params, @create_params)
|> validate_required(@required_create_params)
|> assoc_constraint(:stripe_connect_account)
end
end
16 changes: 15 additions & 1 deletion web/views/stripe_connect_account_view.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
defmodule CodeCorps.StripeConnectAccountView do
use CodeCorps.PreloadHelpers, default_preloads: [:organization]
use CodeCorps.PreloadHelpers, default_preloads: [:organization, :stripe_external_account]
use CodeCorps.Web, :view
use JaSerializer.PhoenixView

alias CodeCorps.StripeConnectAccount

attributes [
:bank_account_bank_name,
:bank_account_last4,
:bank_account_routing_number,
:bank_account_status,
:business_name,
:business_url,
Expand Down Expand Up @@ -75,6 +78,17 @@ defmodule CodeCorps.StripeConnectAccountView do
end
end

def bank_account_bank_name(%{stripe_external_account: nil}, _conn), do: nil
def bank_account_bank_name(%{stripe_external_account: %{bank_name: bank_name}}, _conn), do: bank_name

def bank_account_last4(%{stripe_external_account: nil}, _conn), do: nil
def bank_account_last4(%{stripe_external_account: %{last4: last4}}, _conn), do: last4

def bank_account_routing_number(%{stripe_external_account: nil}, _conn), do: nil
def bank_account_routing_number(%{stripe_external_account: %{routing_number: routing_number}}, _conn), do: routing_number

# TODO: Exteact the 4 status mappings into a module

# recipient_status mapping

def recipient_status(stripe_connect_account, _conn) do
Expand Down