diff --git a/lib/code_corps/stripe_service/adapters/stripe_external_account.ex b/lib/code_corps/stripe_service/adapters/stripe_external_account.ex index 0ee373e39..9950b5816 100644 --- a/lib/code_corps/stripe_service/adapters/stripe_external_account.ex +++ b/lib/code_corps/stripe_service/adapters/stripe_external_account.ex @@ -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 diff --git a/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex b/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex index b70754c72..4fb204d48 100644 --- a/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex +++ b/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex @@ -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 + case Repo.get_by(StripeConnectAccount, id_from_stripe: account_id_from_stripe) do + nil -> {:error, :not_found} + record -> {:ok, record} + end + end end diff --git a/mix.lock b/mix.lock index 8e0d01734..09e16e73e 100644 --- a/mix.lock +++ b/mix.lock @@ -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]}]}, diff --git a/priv/repo/migrations/20170104113708_add_stripe_connect_account_reference_to_external_accounts.exs b/priv/repo/migrations/20170104113708_add_stripe_connect_account_reference_to_external_accounts.exs new file mode 100644 index 000000000..e966e0a81 --- /dev/null +++ b/priv/repo/migrations/20170104113708_add_stripe_connect_account_reference_to_external_accounts.exs @@ -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 diff --git a/test/controllers/stripe_connect_events_controller_test.exs b/test/controllers/stripe_connect_events_controller_test.exs index 9f85ad817..2b26cba2e 100644 --- a/test/controllers/stripe_connect_events_controller_test.exs +++ b/test/controllers/stripe_connect_events_controller_test.exs @@ -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 @@ -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 diff --git a/test/lib/code_corps/stripe_service/adapters/stripe_external_account_test.exs b/test/lib/code_corps/stripe_service/adapters/stripe_external_account_test.exs index 2473196fd..491930379 100644 --- a/test/lib/code_corps/stripe_service/adapters/stripe_external_account_test.exs +++ b/test/lib/code_corps/stripe_service/adapters/stripe_external_account_test.exs @@ -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", @@ -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 diff --git a/test/lib/code_corps/stripe_service/stripe_connect_external_account_service_test.exs b/test/lib/code_corps/stripe_service/stripe_connect_external_account_service_test.exs index 1b908e8e5..ad310cbef 100644 --- a/test/lib/code_corps/stripe_service/stripe_connect_external_account_service_test.exs +++ b/test/lib/code_corps/stripe_service/stripe_connect_external_account_service_test.exs @@ -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 diff --git a/test/support/factories.ex b/test/support/factories.ex index 617568c88..5e84edd7f 100644 --- a/test/support/factories.ex +++ b/test/support/factories.ex @@ -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}"), diff --git a/test/views/stripe_connect_account_view_test.exs b/test/views/stripe_connect_account_view_test.exs index 5d91a94f9..d2400fcb4 100644 --- a/test/views/stripe_connect_account_view_test.exs +++ b/test/views/stripe_connect_account_view_test.exs @@ -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, @@ -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 diff --git a/web/models/stripe_connect_account.ex b/web/models/stripe_connect_account.ex index 26d633b40..f824def9c 100644 --- a/web/models/stripe_connect_account.ex +++ b/web/models/stripe_connect_account.ex @@ -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 diff --git a/web/models/stripe_external_account.ex b/web/models/stripe_external_account.ex index 950a7e274..27ea785f2 100644 --- a/web/models/stripe_external_account.ex +++ b/web/models/stripe_external_account.ex @@ -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] @@ -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 diff --git a/web/views/stripe_connect_account_view.ex b/web/views/stripe_connect_account_view.ex index 1951cf065..56685514d 100644 --- a/web/views/stripe_connect_account_view.ex +++ b/web/views/stripe_connect_account_view.ex @@ -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, @@ -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