-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move user updates into a transaction, which also manages associated p…
…latform and connect customer updates
- Loading branch information
Showing
10 changed files
with
286 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
defmodule CodeCorps.Services.UserService do | ||
@moduledoc """ | ||
Handles CRUD operations for users. | ||
When operations happen on `CodeCorps.User`, we need to make sure changes | ||
are propagated to related records, ex., `CodeCorps.StripePlatformCustomer` or | ||
`CodeCorps.StripeConnectCustomer` | ||
""" | ||
|
||
alias CodeCorps.{Repo, StripeConnectCustomer, StripePlatformCustomer, User} | ||
alias CodeCorps.StripeService.{StripeConnectCustomerService,StripePlatformCustomerService} | ||
alias Ecto.Changeset | ||
alias Ecto.Multi | ||
|
||
@doc """ | ||
Updates a `CodeCorps.User` record and, if necessary, associated | ||
`CodeCorps.StripePlatformCustomer` and `CodeCorps.StripeConnectCustomer` records. | ||
These related records inherit the email field from the user, | ||
so they need to be kept in sync, both locally, and on the Stripe platform. | ||
Returns one of | ||
* `{:ok, %CodeCorps.User{}, :nothing_to_update, :nothing_to_update}` | ||
* `{:ok, %CodeCorps.User{}, %CodeCorps.StripePlatformCustomer{}, :nothing_to_update}` | ||
* `{:ok, %CodeCorps.User{}, %CodeCorps.StripePlatformCustomer{}, %CodeCorps.StripeConnectCustomer{}}` | ||
* `{:error, %Ecto.Changeset{}}` | ||
* `{:error, :unhandled}` | ||
""" | ||
def update(%User{} = user, attributes) do | ||
changeset = user |> User.update_changeset(attributes) | ||
do_update(changeset) | ||
end | ||
|
||
defp do_update(%Changeset{changes: %{email: _email}} = changeset) do | ||
multi = | ||
Multi.new | ||
|> Multi.update(:update_user, changeset) | ||
|> Multi.run(:update_platform_customer, &update_platform_customer/1) | ||
|> Multi.run(:update_connect_customers, &update_connect_customers/1) | ||
|
||
case Repo.transaction(multi) do | ||
{:ok, %{ | ||
update_user: user, | ||
update_platform_customer: update_platform_customer_result, | ||
update_connect_customers: update_connect_customers_results | ||
}} -> | ||
{:ok, user, update_platform_customer_result, update_connect_customers_results} | ||
{:error, :update_user, %Ecto.Changeset{} = changeset, %{}} -> | ||
{:error, changeset} | ||
{:error, _failed_operation, _failed_value, _changes_so_far} -> | ||
{:error, :unhandled} | ||
other -> | ||
IO.inspect(other, pretty: true) | ||
end | ||
end | ||
|
||
defp do_update(%Changeset{} = changeset) do | ||
with {:ok, user} <- Repo.update(changeset) do | ||
{:ok, user, :nothing_to_update, :nothing_to_update} | ||
else | ||
{:error, changeset} -> {:error, changeset} | ||
_ -> {:error, :unhandled} | ||
end | ||
end | ||
|
||
defp update_platform_customer(%{update_user: %User{id: user_id, email: email}}) do | ||
StripePlatformCustomer | ||
|> Repo.get_by(user_id: user_id) | ||
|> do_update_platform_customer(%{email: email}) | ||
end | ||
|
||
defp do_update_platform_customer(nil, _), do: {:ok, :nothing_to_update} | ||
defp do_update_platform_customer(%StripePlatformCustomer{} = stripe_platform_customer, attributes) do | ||
StripePlatformCustomerService.update(stripe_platform_customer, attributes) | ||
end | ||
|
||
defp update_connect_customers(%{update_platform_customer: :nothing_to_update}), do: {:ok, :nothing_to_update} | ||
|
||
defp update_connect_customers(%{update_platform_customer: %StripePlatformCustomer{email: email} = stripe_platform_customer}) do | ||
case do_update_connect_customers(stripe_platform_customer, %{email: email}) do | ||
[_h | _t] = results -> {:ok, results} | ||
[] -> {:ok, :nothing_to_update} | ||
end | ||
end | ||
|
||
defp do_update_connect_customers(stripe_platform_customer, attributes) do | ||
stripe_platform_customer | ||
|> Repo.preload([stripe_connect_customers: :stripe_connect_account]) | ||
|> Map.get(:stripe_connect_customers) | ||
|> Enum.map(&do_update_connect_customer(&1, attributes)) | ||
end | ||
|
||
defp do_update_connect_customer(%StripeConnectCustomer{} = stripe_connect_customer, attributes) do | ||
stripe_connect_customer | ||
|> StripeConnectCustomerService.update(attributes) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
defmodule CodeCorps.Services.UserServiceTest do | ||
use ExUnit.Case, async: true | ||
|
||
use CodeCorps.ModelCase | ||
|
||
alias CodeCorps.StripePlatformCustomer | ||
alias CodeCorps.Services.UserService | ||
|
||
describe "update/1" do | ||
test "it just updates the user if there is nothing associated to update" do | ||
user = insert(:user, email: "mail@mail.com", first_name: "Joe") | ||
|
||
{:ok, user, :nothing_to_update, :nothing_to_update} | ||
= UserService.update(user, %{email: "changed@mail.com"}) | ||
|
||
assert user.email == "changed@mail.com" | ||
assert user.first_name == "Joe" | ||
end | ||
|
||
test "it returns an {:error, changeset} if there are validation errors with the user" do | ||
user = insert(:user, email: "mail@mail.com") | ||
{:error, changeset} = UserService.update(user, %{email: ""}) | ||
|
||
refute changeset.valid? | ||
end | ||
|
||
test "it just updates the user if the changeset does not contain an email" do | ||
user = insert(:user, email: "mail@mail.com") | ||
stripe_platform_customer = insert(:stripe_platform_customer, email: "mail@mail.com", user: user) | ||
|
||
{:ok, user, :nothing_to_update, :nothing_to_update} | ||
= UserService.update(user, %{first_name: "Mark"}) | ||
|
||
assert user.first_name == "Mark" | ||
assert user.email == "mail@mail.com" | ||
|
||
stripe_platform_customer = Repo.get(StripePlatformCustomer, stripe_platform_customer.id) | ||
|
||
assert stripe_platform_customer.email == "mail@mail.com" | ||
end | ||
|
||
test "it also updates the associated platform customer if there is one" do | ||
user = insert(:user, email: "mail@mail.com") | ||
platform_customer = insert(:stripe_platform_customer, user: user) | ||
|
||
{:ok, user, %StripePlatformCustomer{}, :nothing_to_update} | ||
= UserService.update(user, %{email: "changed@mail.com"}) | ||
|
||
assert user.email == "changed@mail.com" | ||
|
||
platform_customer = Repo.get(StripePlatformCustomer, platform_customer.id) | ||
|
||
assert platform_customer.email == "changed@mail.com" | ||
end | ||
|
||
test "it also updates the associated connect customers if there are any" do | ||
user = insert(:user, email: "mail@mail.com") | ||
|
||
platform_customer = %{id_from_stripe: platform_customer_id} | ||
= insert(:stripe_platform_customer, user: user) | ||
|
||
[connect_customer_1, connect_customer_2] = | ||
insert_pair(:stripe_connect_customer, stripe_platform_customer: platform_customer) | ||
|
||
{:ok, user, %StripePlatformCustomer{}, connect_updates} = UserService.update(user, %{email: "changed@mail.com"}) | ||
|
||
assert user.email == "changed@mail.com" | ||
|
||
platform_customer = Repo.get_by(StripePlatformCustomer, id_from_stripe: platform_customer_id) | ||
assert platform_customer.email == "changed@mail.com" | ||
|
||
[ | ||
{:ok, %Stripe.Customer{} = stripe_record_1}, | ||
{:ok, %Stripe.Customer{} = stripe_record_2} | ||
] = connect_updates | ||
|
||
assert stripe_record_1.id == connect_customer_1.id_from_stripe | ||
assert stripe_record_1.email == "changed@mail.com" | ||
assert stripe_record_2.id == connect_customer_2.id_from_stripe | ||
assert stripe_record_2.email == "changed@mail.com" | ||
end | ||
end | ||
end |
21 changes: 21 additions & 0 deletions
21
test/lib/code_corps/stripe_service/stripe_platform_customer_service_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
defmodule CodeCorps.StripeService.StripePlatformCustomerServiceTest do | ||
use CodeCorps.ModelCase | ||
|
||
alias CodeCorps.StripeService.StripePlatformCustomerService | ||
|
||
describe "update/2" do | ||
test "performs update" do | ||
customer = insert(:stripe_platform_customer) | ||
{:ok, customer} = StripePlatformCustomerService.update(customer, %{email: "mail@mail.com"}) | ||
assert customer.email == "mail@mail.com" | ||
|
||
# TODO: Figure out testing if stripe API request was made | ||
end | ||
|
||
test "returns changeset with validation errors if there is an issue" do | ||
customer = insert(:stripe_platform_customer) | ||
{:error, changeset} = StripePlatformCustomerService.update(customer, %{email: nil}) | ||
refute changeset.valid? | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters