diff --git a/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex b/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex index 373940efe..bceb5c430 100644 --- a/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex +++ b/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex @@ -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") 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 9950b5816..5a9ddd694 100644 --- a/lib/code_corps/stripe_service/adapters/stripe_external_account.ex +++ b/lib/code_corps/stripe_service/adapters/stripe_external_account.ex @@ -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 diff --git a/lib/code_corps/stripe_service/stripe_connect_account.ex b/lib/code_corps/stripe_service/stripe_connect_account.ex deleted file mode 100644 index 11d745c22..000000000 --- a/lib/code_corps/stripe_service/stripe_connect_account.ex +++ /dev/null @@ -1,49 +0,0 @@ -defmodule CodeCorps.StripeService.StripeConnectAccountService do - alias CodeCorps.{Repo, StripeConnectAccount} - alias CodeCorps.StripeService.Adapters.{StripeConnectAccountAdapter} - - @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} = account, %{} = attributes) do - with {:ok, from_params} <- StripeConnectAccountAdapter.from_params(attributes), - {:ok, %Stripe.Account{} = stripe_account} <- @api.Account.update(id_from_stripe, from_params), - {:ok, params} <- StripeConnectAccountAdapter.to_params(stripe_account, attributes) - do - account |> StripeConnectAccount.webhook_update_changeset(params) |> Repo.update - 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{} = stripe_account} <- @api.Account.retrieve(id_from_stripe), - %StripeConnectAccount{} = local_account <- Repo.get_by(StripeConnectAccount, id_from_stripe: id_from_stripe), - {:ok, params} <- stripe_account |> StripeConnectAccountAdapter.to_params(%{}) - do - local_account |> StripeConnectAccount.webhook_update_changeset(params) |> Repo.update - else - nil -> {:error, :not_found} - failure -> failure - end - end -end diff --git a/lib/code_corps/stripe_service/stripe_connect_account_service.ex b/lib/code_corps/stripe_service/stripe_connect_account_service.ex new file mode 100644 index 000000000..eee25c1c9 --- /dev/null +++ b/lib/code_corps/stripe_service/stripe_connect_account_service.ex @@ -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 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 f1092e6aa..628b667cb 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 @@ -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 diff --git a/lib/code_corps/stripe_service/webhook_processing/connect_event_handler.ex b/lib/code_corps/stripe_service/webhook_processing/connect_event_handler.ex index 07f72e04f..bcc819cd3 100644 --- a/lib/code_corps/stripe_service/webhook_processing/connect_event_handler.ex +++ b/lib/code_corps/stripe_service/webhook_processing/connect_event_handler.ex @@ -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) diff --git a/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex b/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex index 2f2b692bb..2f348bcd6 100644 --- a/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex +++ b/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex @@ -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", @@ -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" diff --git a/lib/code_corps/stripe_testing/account.ex b/lib/code_corps/stripe_testing/account.ex index b142f7358..233ff35a2 100644 --- a/lib/code_corps/stripe_testing/account.ex +++ b/lib/code_corps/stripe_testing/account.ex @@ -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 @@ -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" diff --git a/lib/code_corps/stripe_testing/fixtures/account_with_multiple_external_accounts.json b/lib/code_corps/stripe_testing/fixtures/account_with_multiple_external_accounts.json new file mode 100644 index 000000000..236c783b4 --- /dev/null +++ b/lib/code_corps/stripe_testing/fixtures/account_with_multiple_external_accounts.json @@ -0,0 +1,128 @@ +{ + "id": "account_with_multiple_external_accounts", + "object": "account", + "business_logo": null, + "business_name": "Some Company Inc.", + "business_url": "somecompany.org", + "charges_enabled": false, + "country": "US", + "debit_negative_balances": true, + "decline_charge_on": { + "avs_failure": false, + "cvc_failure": true + }, + "default_currency": "usd", + "details_submitted": false, + "display_name": "Code Corps", + "email": "someone@mail.com", + "external_accounts": { + "object": "list", + "data": [ + { + "id": "ba_111111111111111111111111", + "object": "bank_account", + "account": "account_with_multiple_external_accounts", + "account_holder_name": "Jane Austen", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "default_for_currency": false, + "fingerprint": "3wib8M6m7DxSV0PP", + "last4": "6789", + "metadata": { + }, + "routing_number": "110000000", + "status": "new" + }, + { + "id": "ba_222222222222222222222222", + "object": "bank_account", + "account": "account_with_multiple_external_accounts", + "account_holder_name": "Jane Austen", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "default_for_currency": false, + "fingerprint": "3wib8M6m7DxSV0PP", + "last4": "6789", + "metadata": { + }, + "routing_number": "110000000", + "status": "new" + } + ], + "has_more": false, + "total_count": 0, + "url": "/v1/accounts/account_with_multiple_external_accounts/external_accounts" + }, + "legal_entity": { + "additional_owners": [ + + ], + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "business_name": "Some Company Inc.", + "business_tax_id_provided": false, + "business_vat_id_provided": false, + "dob": { + "day": null, + "month": null, + "year": null + }, + "first_name": "John", + "last_name": "Doe", + "personal_address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "personal_id_number_provided": false, + "ssn_last_4_provided": false, + "type": "sole_prop", + "verification": { + "details": null, + "details_code": "failed_other", + "document": "fil_12345", + "status": "unverified" + } + }, + "managed": true, + "metadata": null, + "product_description": "Some product description.", + "statement_descriptor": null, + "support_email": null, + "support_phone": "1234567890", + "timezone": "Europe/Zagreb", + "tos_acceptance": { + "date": null, + "ip": null, + "user_agent": null + }, + "transfer_schedule": { + "delay_days": 2, + "interval": "daily" + }, + "transfer_statement_descriptor": null, + "transfers_enabled": false, + "verification": { + "disabled_reason": "fields_needed", + "due_by": null, + "fields_needed": [ + "business_url", + "external_account", + "tos_acceptance.date", + "tos_acceptance.ip" + ] + } +} diff --git a/lib/code_corps/stripe_testing/helpers.ex b/lib/code_corps/stripe_testing/helpers.ex new file mode 100644 index 000000000..c3e2612b5 --- /dev/null +++ b/lib/code_corps/stripe_testing/helpers.ex @@ -0,0 +1,16 @@ +defmodule CodeCorps.StripeTesting.Helpers do + @fixture_path "./lib/code_corps/stripe_testing/fixtures/" + + @doc """ + Load a stripe response fixture through stripity_stripe, into a + stripity_stripe struct + """ + def load_fixture(module, id) do + fixture_map = id |> build_file_path |> File.read! |> Poison.decode! + Stripe.Converter.stripe_map_to_struct(module, fixture_map) + end + + defp build_file_path(id), do: id |> append_extension |> join_with_path + defp append_extension(id), do: id <> ".json" + defp join_with_path(filename), do: @fixture_path <> filename +end diff --git a/mix.lock b/mix.lock index 7968b778c..fd7e415d5 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", "9e74bd330e3013b5fd8e13874177d1bbf27d4bbf", [branch: "2.0"]}, + "stripity_stripe": {:git, "https://github.com/code-corps/stripity_stripe.git", "effa8da9b6fa4fed4cebb49624404871e2a04a1b", [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/test/lib/code_corps/stripe_service/adapters/stripe_connect_account_test.exs b/test/lib/code_corps/stripe_service/adapters/stripe_connect_account_test.exs index a82e4ba9f..3d9e4c1d3 100644 --- a/test/lib/code_corps/stripe_service/adapters/stripe_connect_account_test.exs +++ b/test/lib/code_corps/stripe_service/adapters/stripe_connect_account_test.exs @@ -13,9 +13,10 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do details_submitted: false, display_name: "Code Corps", email: "volunteers@codecorps.org", - external_accounts: %{ - object: "list", - data: [%{"id" => "ba_123"}], + external_accounts: %Stripe.List{ + data: [ + %Stripe.ExternalAccount{id: "ba_123"} + ], has_more: false, total_count: 0, url: "/v1/accounts/acct_123/external_accounts" @@ -85,19 +86,25 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do } } + defp test_account do + # If a `Stripe.Account` has multiple `Stripe.ExternalAccount` records, we want + # the adapter to deal with that by only taking one, so we load the appropriate fixture + CodeCorps.StripeTesting.Helpers.load_fixture(Stripe.Account, "account_with_multiple_external_accounts") + end + @local_map %{ - "id_from_stripe" => "acct_123", + "id_from_stripe" => "account_with_multiple_external_accounts", - "business_name" => "Code Corps PBC", - "business_url" => "codecorps.org", + "business_name" => "Some Company Inc.", + "business_url" => "somecompany.org", "charges_enabled" => false, "country" => "US", "default_currency" => "usd", "details_submitted" => false, "display_name" => "Code Corps", - "email" => "volunteers@codecorps.org", + "email" => "someone@mail.com", - "external_account" => "ba_123", + "external_account" => "ba_222222222222222222222222", "legal_entity_address_city" => nil, "legal_entity_address_country" => "US", @@ -106,19 +113,19 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do "legal_entity_address_postal_code" => nil, "legal_entity_address_state" => nil, - "legal_entity_business_name" => nil, - "legal_entity_business_tax_id" => "000000000", - "legal_entity_business_tax_id_provided" => true, - "legal_entity_business_vat_id" => "000000000", - "legal_entity_business_vat_id_provided" => true, + "legal_entity_business_name" => "Some Company Inc.", + "legal_entity_business_tax_id" => nil, + "legal_entity_business_tax_id_provided" => false, + "legal_entity_business_vat_id" => nil, + "legal_entity_business_vat_id_provided" => false, "legal_entity_dob_day" => nil, "legal_entity_dob_month" => nil, "legal_entity_dob_year" => nil, - "legal_entity_first_name" => nil, + "legal_entity_first_name" => "John", "legal_entity_gender" => nil, - "legal_entity_last_name" => nil, + "legal_entity_last_name" => "Doe", "legal_entity_maiden_name" => nil, "legal_entity_personal_address_city" => nil, @@ -136,14 +143,14 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do "legal_entity_ssn_last_4" => nil, "legal_entity_ssn_last_4_provided" => false, - "legal_entity_type" => nil, + "legal_entity_type" => "sole_prop", "legal_entity_verification_details" => nil, "legal_entity_verification_details_code" => "failed_other", "legal_entity_verification_document" => "fil_12345", "legal_entity_verification_status" => "unverified", - "managed" => false, + "managed" => true, "support_email" => nil, "support_phone" => "1234567890", @@ -151,17 +158,15 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do "transfers_enabled" => false, - "tos_acceptance_date" => 123456, - "tos_acceptance_ip" => "127.0.0.1", - "tos_acceptance_user_agent" => "Chrome", + "tos_acceptance_date" => nil, + "tos_acceptance_ip" => nil, + "tos_acceptance_user_agent" => nil, "verification_disabled_reason" => "fields_needed", "verification_due_by" => nil, "verification_fields_needed" => [ "business_url", "external_account", - "product_description", - "support_phone", "tos_acceptance.date", "tos_acceptance.ip" ] @@ -169,15 +174,10 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTest do describe "to_params/2" do test "converts from stripe map to local properly" do - test_attributes = %{ - "organization_id" => 123, - "foo" => "bar" - } - expected_attributes = %{ - "organization_id" => 123, - } + test_attributes = %{"organization_id" => 123, "foo" => "bar"} + expected_attributes = %{"organization_id" => 123,} - {:ok, result} = to_params(@stripe_connect_account, test_attributes) + {:ok, result} = to_params(test_account, test_attributes) expected_map = Map.merge(@local_map, expected_attributes) assert result == expected_map 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 08d0d40ee..989eb48d1 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 @@ -1,7 +1,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountTest do - use ExUnit.Case, async: true + use CodeCorps.ModelCase - import CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter, only: [to_params: 1] + import CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter, only: [to_params: 2] @stripe_external_account %Stripe.ExternalAccount{ id: "ba_19SSZG2eZvKYlo2CXnmzYU5H", @@ -22,7 +22,6 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountTest do @local_map %{ id_from_stripe: "ba_19SSZG2eZvKYlo2CXnmzYU5H", - account_id_from_stripe: "acct_1032D82eZvKYlo2C", account_holder_name: "Jane Austen", account_holder_type: "individual", bank_name: "STRIPE TEST BANK", @@ -32,14 +31,22 @@ defmodule CodeCorps.StripeService.Adapters.StripeExternalAccountTest do fingerprint: "1JWtPxqbdX5Gamtc", last4: "6789", routing_number: "110000000", - status: "new", - stripe_connect_account_id: nil + status: "new" } describe "to_params/2" do test "converts from stripe map to local properly" do - {:ok, result} = to_params(@stripe_external_account) - assert result == @local_map + connect_account = insert(:stripe_connect_account) + + attrs_from_connect_account = %{ + stripe_connect_account_id: connect_account.id, + account_id_from_stripe: connect_account.id_from_stripe + } + + expected_result = @local_map |> Map.merge(attrs_from_connect_account) + + {:ok, result} = to_params(@stripe_external_account, connect_account) + assert result == expected_result end end end diff --git a/test/lib/code_corps/stripe_service/stripe_connect_account_service_test.exs b/test/lib/code_corps/stripe_service/stripe_connect_account_service_test.exs index cd644fde9..f293abd8a 100644 --- a/test/lib/code_corps/stripe_service/stripe_connect_account_service_test.exs +++ b/test/lib/code_corps/stripe_service/stripe_connect_account_service_test.exs @@ -1,9 +1,7 @@ defmodule CodeCorps.StripeService.StripeConnectAccountServiceTest do - use ExUnit.Case, async: true + use CodeCorps.StripeCase - use CodeCorps.ModelCase - - alias CodeCorps.{StripeConnectAccount} + alias CodeCorps.{StripeConnectAccount, StripeExternalAccount} alias CodeCorps.StripeService.StripeConnectAccountService describe "create" do @@ -27,12 +25,50 @@ defmodule CodeCorps.StripeService.StripeConnectAccountServiceTest do end describe "update/2" do - test "assigns the external_account property to the record" do - account = insert(:stripe_connect_account) + test "assigns the external_account property to the record, creates external account" do + connect_account = insert(:stripe_connect_account) {:ok, %StripeConnectAccount{} = updated_account} = - StripeConnectAccountService.update(account, %{"external_account" =>"ba_123"}) + StripeConnectAccountService.update(connect_account, %{"external_account" =>"ba_123"}) assert updated_account.external_account == "ba_123" + + assert Repo.get_by(StripeExternalAccount, stripe_connect_account_id: connect_account.id) + end + end + + describe "update_from_stripe/1" do + test "updates connect account with stripe information, creates external_account" do + # we use a preset fixture from StripeTesting + # the fixture is for multiple external accounts, because we want to make sure + # that part is not failing due to us only supporting a has_one relationship + id_from_stripe = "account_with_multiple_external_accounts" + + connect_account = insert(:stripe_connect_account, id_from_stripe: id_from_stripe) + + {:ok, %StripeConnectAccount{} = updated_account} = + StripeConnectAccountService.update_from_stripe(id_from_stripe) + + assert updated_account.business_name == "Some Company Inc." + + assert Repo.get_by(StripeExternalAccount, stripe_connect_account_id: connect_account.id) + end + + test "deletes old external account, if it exists" do + # we use a preset fixture from StripeTesting + # the fixture is for multiple external accounts, because we want to make sure + # that part is not failing due to us only supporting a has_one relationship + id_from_stripe = "account_with_multiple_external_accounts" + + connect_account = insert(:stripe_connect_account, id_from_stripe: id_from_stripe) + external_account = insert(:stripe_external_account, stripe_connect_account: connect_account) + + {:ok, %StripeConnectAccount{} = updated_account} = + StripeConnectAccountService.update_from_stripe(id_from_stripe) + + assert updated_account.business_name == "Some Company Inc." + + assert Repo.get(StripeExternalAccount, external_account.id) == nil + assert Repo.get_by(StripeExternalAccount, stripe_connect_account_id: connect_account.id) end 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 ad310cbef..ef15a0c27 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 @@ -1,29 +1,19 @@ defmodule CodeCorps.StripeService.StripeConnectExternalAccountServiceTest do - use ExUnit.Case, async: true - use CodeCorps.ModelCase alias CodeCorps.StripeService.StripeConnectExternalAccountService describe "create" do test "creates a StripeExternalAccount" do - id_from_stripe = "ba_testing123" - account_id_from_stripe = "acct_123" - - connect_account = insert(:stripe_connect_account, id_from_stripe: account_id_from_stripe) + api_external_account = %Stripe.ExternalAccount{id: "bnk_123"} + local_connect_account = insert(:stripe_connect_account) {:ok, %CodeCorps.StripeExternalAccount{} = external_account} = - StripeConnectExternalAccountService.create(id_from_stripe, account_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" + StripeConnectExternalAccountService.create(api_external_account, local_connect_account) - assert {:error, :not_found} == StripeConnectExternalAccountService.create(id_from_stripe, account_id_from_stripe) + assert external_account.id_from_stripe == "bnk_123" + assert external_account.stripe_connect_account_id == local_connect_account.id + assert external_account.account_id_from_stripe == local_connect_account.id_from_stripe end end end diff --git a/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs b/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs index 88ccac7bb..5016ac407 100644 --- a/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs +++ b/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs @@ -6,7 +6,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest do } alias CodeCorps.{ - StripeEvent, StripeExternalAccount, StripeInvoice, StripePlatformCard, StripePlatformCustomer + StripeEvent, StripeInvoice, StripePlatformCard, StripePlatformCustomer } defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest.StubObject do @@ -58,21 +58,6 @@ defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest do end describe "connect events" do - test "handles account.external_account.created" do - connect_account = insert(:stripe_connect_account) - event = build_event( - "account.external_account.created", - %Stripe.ExternalAccount{id: "ext_123", account: connect_account.id_from_stripe} - ) - - {:ok, event} = EventHandler.handle(event, ConnectEventHandler) - assert event.object_type == "external_account" - assert event.object_id == "ext_123" - assert event.status == "processed" - - assert Repo.get_by(StripeExternalAccount, id_from_stripe: "ext_123") - end - test "handles account.updated" do connect_account = insert(:stripe_connect_account) event = build_event("account.updated", %Stripe.Account{id: connect_account.id_from_stripe}) diff --git a/test/support/stripe_case.ex b/test/support/stripe_case.ex new file mode 100644 index 000000000..2a5869659 --- /dev/null +++ b/test/support/stripe_case.ex @@ -0,0 +1,32 @@ +defmodule CodeCorps.StripeCase do + @moduledoc """ + This module defines the test case to be used by + tests involving the stripe service. + + Basically a stripped down `CodeCorps.ModelCase` + """ + + use ExUnit.CaseTemplate + + using do + quote do + alias CodeCorps.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import CodeCorps.Factories + import CodeCorps.StripeCase + end + end + + setup tags do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(CodeCorps.Repo) + + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(CodeCorps.Repo, {:shared, self()}) + end + + :ok + end +end