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 351eedd47..5c4e66319 100644 --- a/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex +++ b/lib/code_corps/stripe_service/adapters/stripe_connect_account.ex @@ -66,6 +66,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do stripe_account |> Map.from_struct |> transform_map(@stripe_mapping) + |> add_nested_attributes(stripe_account) |> keys_to_string |> add_non_stripe_attributes(attributes) @@ -91,4 +92,13 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do params |> Map.merge(attributes) end + + defp add_nested_attributes(map, stripe_account) 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) end diff --git a/lib/code_corps/stripe_service/stripe_connect_account.ex b/lib/code_corps/stripe_service/stripe_connect_account.ex index 0cf599661..40a7836e6 100644 --- a/lib/code_corps/stripe_service/stripe_connect_account.ex +++ b/lib/code_corps/stripe_service/stripe_connect_account.ex @@ -1,17 +1,26 @@ defmodule CodeCorps.StripeService.StripeConnectAccountService do + alias CodeCorps.{Repo, StripeConnectAccount} alias CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter @api Application.get_env(:code_corps, :stripe) - # TODO: Replace with code that implements issue #564 - - def create(%{"country" => country_code, "organization_id" => organization_id} = attributes) do + def create(%{"country" => country_code, "organization_id" => _} = attributes) do with {:ok, %Stripe.Account{} = account} <- @api.Account.create(%{country: country_code, managed: true}), {:ok, params} <- StripeConnectAccountAdapter.to_params(account, attributes) do - %CodeCorps.StripeConnectAccount{} - |> CodeCorps.StripeConnectAccount.create_changeset(params) - |> CodeCorps.Repo.insert + %StripeConnectAccount{} + |> StripeConnectAccount.create_changeset(params) + |> Repo.insert + end + end + + def add_external_account(%StripeConnectAccount{id_from_stripe: stripe_id} = record, external_account) do + with {:ok, %Stripe.Account{} = stripe_account} <- @api.Account.update(stripe_id, %{external_account: external_account}), + {:ok, params} <- StripeConnectAccountAdapter.to_params(stripe_account, %{}) + do + record + |> StripeConnectAccount.stripe_update_changeset(params) + |> Repo.update end end end diff --git a/lib/code_corps/stripe_testing/account.ex b/lib/code_corps/stripe_testing/account.ex index 3b7c32e7f..2a3cb24aa 100644 --- a/lib/code_corps/stripe_testing/account.ex +++ b/lib/code_corps/stripe_testing/account.ex @@ -1,32 +1,83 @@ defmodule CodeCorps.StripeTesting.Account do def create(_map) do - {:ok, do_create} - end - - def retrieve(_id) do - {:ok, do_create} - end - - defp do_create do - %Stripe.Account{ - business_name: "Code Corps PBC", - business_primary_color: nil, - business_url: "codecorps.org", - charges_enabled: true, - country: "US", - default_currency: "usd", - details_submitted: true, - display_name: "Code Corps Customer", - email: "volunteers@codecorps.org", - id: "acct_123", - managed: true, - metadata: %{}, - statement_descriptor: "CODECORPS.ORG", - support_email: nil, - support_phone: "1234567890", - support_url: nil, - timezone: "America/Los_Angeles", - transfers_enabled: true + {:ok, create_stripe_record(%{})} + end + + def retrieve(id) do + {:ok, create_stripe_record(%{"id" => id})} + end + + def update(id, attributes) do + attributes = + attributes + |> CodeCorps.MapUtils.keys_to_string + |> Map.merge(%{"id" => id}) + + {:ok, create_stripe_record(attributes)} + end + + defp create_stripe_record(attributes) do + with attributes <- account_fixture |> Map.merge(attributes) |> add_nestings + do + Stripe.Account |> Stripe.Converter.stripe_map_to_struct(attributes) + end + end + + defp account_fixture do + %{ + "business_name" => "Code Corps PBC", + "business_primary_color" => nil, + "business_url" => "codecorps.org", + "charges_enabled" => true, + "country" => "US", + "default_currency" => "usd", + "details_submitted" => true, + "display_name" => "Code Corps Customer", + "email" => "volunteers@codecorps.org", + "external_accounts" => %{ + "object" => "list", + "data" => [], + "has_more" => false, + "total_count" => 0, + "url" => "/v1/accounts/acct_123/external_accounts" + }, + "id" => "acct_123", + "managed" => true, + "metadata" => %{}, + "statement_descriptor" => "CODECORPS.ORG", + "support_email" => nil, + "support_phone" => "1234567890", + "support_url" => nil, + "timezone" => "America/Los_Angeles", + "transfers_enabled" => true } end + + defp add_nestings(map) do + map + |> add_external_account + end + + defp add_external_account(%{"id" => account_id, "external_account" => external_account_id} = map) do + external_accounts_map = %{ + "object" => "list", + "data" => [%{"id" => external_account_id}], + "has_more" => false, + "total_count" => 1, + "url" => "/v1/accounts/#{account_id}/external_accounts" + } + + Map.put(map, "external_accounts", external_accounts_map) + end + defp add_external_account(%{"id" => account_id} = map) do + external_accounts_map = %{ + "object" => "list", + "data" => [], + "has_more" => false, + "total_count" => 1, + "url" => "/v1/accounts/#{account_id}/external_accounts" + } + + Map.put(map, "external_accounts", external_accounts_map) + end end diff --git a/priv/repo/migrations/20161219163909_add_external_account_to_stripe_connect_accounts.exs b/priv/repo/migrations/20161219163909_add_external_account_to_stripe_connect_accounts.exs new file mode 100644 index 000000000..04390d315 --- /dev/null +++ b/priv/repo/migrations/20161219163909_add_external_account_to_stripe_connect_accounts.exs @@ -0,0 +1,9 @@ +defmodule CodeCorps.Repo.Migrations.AddExternalAccountToStripeConnectAccounts do + use Ecto.Migration + + def change do + alter table(:stripe_connect_accounts) do + add :external_account, :string + end + end +end diff --git a/test/controllers/stripe_connect_account_controller_test.exs b/test/controllers/stripe_connect_account_controller_test.exs index 37f500747..c5e091668 100644 --- a/test/controllers/stripe_connect_account_controller_test.exs +++ b/test/controllers/stripe_connect_account_controller_test.exs @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeConnectAccountControllerTest do use CodeCorps.ApiCase, resource_name: :stripe_connect_account + alias CodeCorps.StripeConnectAccount + describe "show" do @tag :authenticated test "shows chosen resource", %{conn: conn, current_user: current_user} do @@ -29,7 +31,7 @@ defmodule CodeCorps.StripeConnectAccountControllerTest do describe "create" do @tag :authenticated - test "creates and renders resource user is authenticated and authorized", %{conn: conn, current_user: current_user} do + test "creates and renders resource when user is authenticated and authorized", %{conn: conn, current_user: current_user} do organization = insert(:organization) insert(:organization_membership, member: current_user, organization: organization, role: "owner") attrs = %{ organization: organization } @@ -50,4 +52,33 @@ defmodule CodeCorps.StripeConnectAccountControllerTest do assert conn |> request_create(attrs) |> json_response(403) end end + + describe "update" do + @tag :authenticated + test "updates external account on resource when user is authenticated and authorized", %{conn: conn, current_user: current_user} do + organization = insert(:organization) + + insert(:organization_membership, member: current_user, organization: organization, role: "owner") + stripe_connect_account = insert(:stripe_connect_account, organization: organization) + + attrs = %{external_account: "ba_test123"} + + assert conn |> request_update(stripe_connect_account, attrs, :skip_strategy) |> json_response(200) + + updated_account = Repo.get(StripeConnectAccount, stripe_connect_account.id) + assert updated_account.external_account == "ba_test123" + end + + test "does not update resource and renders 401 when unauthenticated", %{conn: conn} do + assert conn |> request_update |> json_response(401) + end + + @tag :authenticated + test "does not update resource and renders 403 when not authorized", %{conn: conn} do + organization = insert(:organization) + stripe_connect_account = insert(:stripe_connect_account, organization: organization) + + assert conn |> request_update(stripe_connect_account, %{}) |> json_response(403) + end + end end 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 fa8d1e428..3d1e42de6 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 @@ -15,7 +15,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTestAdapter do email: "volunteers@codecorps.org", external_accounts: %{ object: "list", - data: [], + data: [%{"id" => "ba_123"}], has_more: false, total_count: 0, url: "/v1/accounts/acct_123/external_accounts" @@ -56,6 +56,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTestAdapter do status: "unverified" } }, + id: "acct_123", managed: false, statement_descriptor: nil, support_email: nil, @@ -87,6 +88,8 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountTestAdapter do "display_name" => "Code Corps", "email" => "volunteers@codecorps.org", + "external_account" => "ba_123", + "legal_entity_address_city" => nil, "legal_entity_address_country" => "US", "legal_entity_address_line1" => nil, 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 b15e5aaec..abbae3b11 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 @@ -3,6 +3,7 @@ defmodule CodeCorps.StripeService.StripeConnectAccountServiceTest do use CodeCorps.ModelCase + alias CodeCorps.{StripeConnectAccount} alias CodeCorps.StripeService.StripeConnectAccountService describe "create" do @@ -11,12 +12,22 @@ defmodule CodeCorps.StripeService.StripeConnectAccountServiceTest do attributes = %{"country" => "US", "organization_id" => organization.id} - {:ok, %CodeCorps.StripeConnectAccount{} = connect_account} = - StripeConnectAccountService.create(attributes) + {:ok, %StripeConnectAccount{} = connect_account} = + StripeConnectAccountService.create(attributes) assert connect_account.country == "US" assert connect_account.organization_id == organization.id assert connect_account.managed == true end end + + describe "add_external_account/2" do + test "assigns the external_account property to the record" do + account = insert(:stripe_connect_account) + + {:ok, %StripeConnectAccount{} = updated_account} = + StripeConnectAccountService.add_external_account(account, "ba_test123") + assert updated_account.external_account == "ba_test123" + end + end end diff --git a/test/support/api_case.ex b/test/support/api_case.ex index d264f128c..af1b1753a 100644 --- a/test/support/api_case.ex +++ b/test/support/api_case.ex @@ -119,6 +119,15 @@ defmodule CodeCorps.ApiCase do conn |> put(path, payload) end + # A temporary request_update that skips building a payload + # using our faulty json_payload strategy + # Only works with attributes, not relationships + def request_update(conn, resource_or_id, attrs, :skip_strategy) do + payload = %{data: %{attributes: attrs}, type: "#{factory_name}"} + path = conn |> path_for(:update, resource_or_id) + conn |> put(path, payload) + end + def request_delete(conn), do: request_delete(conn, default_record) def request_delete(conn, :not_found), do: request_delete(conn, -1) def request_delete(conn, resource_or_id) do diff --git a/web/controllers/stripe_connect_account_controller.ex b/web/controllers/stripe_connect_account_controller.ex index e81ba9bbd..2cbbaa412 100644 --- a/web/controllers/stripe_connect_account_controller.ex +++ b/web/controllers/stripe_connect_account_controller.ex @@ -19,4 +19,21 @@ defmodule CodeCorps.StripeConnectAccountController do defp handle_create_result({:ok, %StripeConnectAccount{}} = result, conn) do result |> CodeCorps.Analytics.Segment.track(:created, conn) end + + def handle_update(conn, record, %{"external_account" => external_account}) do + with {:ok, _} = result <- StripeConnectAccountService.add_external_account(record, external_account) + do + CodeCorps.Analytics.Segment.track(result, :created, conn) + else + {:error, %Ecto.Changeset{} = changeset} -> changeset + end + end + + def handle_update(conn, _record, _attributes), do: conn |> unauthorized + + defp unauthorized(conn) do + conn + |> Plug.Conn.assign(:authorized, false) + |> CodeCorps.AuthenticationHelpers.handle_unauthorized + end end diff --git a/web/models/stripe_connect_account.ex b/web/models/stripe_connect_account.ex index 6827ddf73..81e08442d 100644 --- a/web/models/stripe_connect_account.ex +++ b/web/models/stripe_connect_account.ex @@ -15,6 +15,8 @@ defmodule CodeCorps.StripeConnectAccount do field :display_name, :string field :email, :string + field :external_account, :string + field :legal_entity_address_city, :string field :legal_entity_address_country, :string field :legal_entity_address_line1, :string @@ -115,4 +117,17 @@ defmodule CodeCorps.StripeConnectAccount do struct |> cast(params, @webhook_update_params) end + + @update_params [ + :business_name, :business_url, :charges_enabled, :country, :default_currency, + :details_submitted, :email, :external_account, :id_from_stripe, + :support_email, :support_phone, :support_url, :transfers_enabled, + :verification_disabled_reason, :verification_due_by, + :verification_fields_needed + ] + + def stripe_update_changeset(struct, params) do + struct + |> cast(params, @update_params) + end end diff --git a/web/router.ex b/web/router.ex index c48c92a94..f77f3609f 100644 --- a/web/router.ex +++ b/web/router.ex @@ -68,7 +68,7 @@ defmodule CodeCorps.Router do resources "/roles", RoleController, only: [:create] resources "/role-skills", RoleSkillController, only: [:create, :delete] resources "/skills", SkillController, only: [:create] - resources "/stripe-connect-accounts", StripeConnectAccountController, only: [:show, :create] + resources "/stripe-connect-accounts", StripeConnectAccountController, only: [:show, :create, :update] resources "/stripe-connect-plans", StripeConnectPlanController, only: [:show, :create] resources "/stripe-connect-subscriptions", StripeConnectSubscriptionController, only: [:show, :create] resources "/stripe-platform-cards", StripePlatformCardController, only: [:show, :create]