Skip to content

Commit

Permalink
Merge pull request #664 from code-corps/590-simplify-subscription-cre…
Browse files Browse the repository at this point in the history
…ation

Simplified subscription creation
  • Loading branch information
joshsmith committed Jan 30, 2017
2 parents a935a96 + 195d338 commit 575134d
Show file tree
Hide file tree
Showing 33 changed files with 157 additions and 117 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
import Ecto.Query

alias CodeCorps.{
Project, Repo, StripeConnectCustomer, StripeConnectAccount,
StripeConnectPlan, StripeConnectSubscription, User
Expand All @@ -20,19 +18,22 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
- `{:ok, %StripeConnectSubscription{}}` - the created record.
- `{:error, %Ecto.Changeset{}}` - the record was not created due to validation issues.
- `{:error, :project_not_found}`
- `{:error, :project_not_ready}` - the associated project does not meed the prerequisites for receiving donations.
- `{:error, :user_not_found}`
- `{:error, :user_not_ready}` - the associated user does not meet the prerequisits to donate.
- `{:error, %Stripe.APIErrorResponse{}}` - there was a problem with the stripe request
- `{:error, :not_found}` - one of the associated records was not found
# Side effects
- If the subscription is created or found, associated project totals will get updated
- If the subscription is created or found, associated donation goal states will be updated
"""
def find_or_create(%{"project_id" => project_id, "quantity" => _, "user_id" => user_id} = attributes) do
with {:ok, %Project{} = project} <- get_project(project_id) |> ProjectSubscribable.validate,
{:ok, %User{} = user} <- get_user(user_id) |> UserCanSubscribe.validate
with {:ok, %Project{} = project} <- get_project_with_preloads(project_id),
{:ok, %Project{}} <- ProjectSubscribable.validate(project),
{:ok, %User{} = user} <- get_user_with_preloads(user_id),
{:ok, %User{}} <- UserCanSubscribe.validate(user)
do
{:ok, %StripeConnectSubscription{} = subscription} = do_find_or_create(project, user, attributes)

Expand All @@ -41,7 +42,6 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do

{:ok, subscription}
else
nil -> {:error, :not_found}
failure -> failure
end
end
Expand All @@ -59,9 +59,8 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
def update_from_stripe(stripe_id, connect_customer_id) do
with {:ok, %StripeConnectAccount{} = connect_account} <- retrieve_connect_account(connect_customer_id),
{:ok, %Stripe.Subscription{} = stripe_subscription} <- @api.Subscription.retrieve(stripe_id, connect_account: connect_account.id),
{:ok, %StripeConnectSubscription{} = subscription} <- load_subscription(stripe_id),
{:ok, params} <- stripe_subscription |> StripeConnectSubscriptionAdapter.to_params(%{}),
{:ok, %Project{} = project} <- get_project(subscription)
{:ok, %StripeConnectSubscription{stripe_connect_plan: %{project: project}} = subscription} <- load_subscription(stripe_id),
{:ok, params} <- stripe_subscription |> StripeConnectSubscriptionAdapter.to_params(%{})
do
{:ok, %StripeConnectSubscription{} = subscription} = update_subscription(subscription, params)

Expand All @@ -75,29 +74,30 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
end
end

defp do_find_or_create(%Project{} = project, %User{} = user, %{} = attributes) do
case find(project, user) do
# find_or_create

defp do_find_or_create(project, user, attributes) do
case find(project.stripe_connect_plan, user) do
nil -> create(project, user, attributes)
%StripeConnectSubscription{} = subscription -> {:ok, subscription}
end
end

defp find(%Project{} = project, %User{} = user) do
defp find(plan, user) do
StripeConnectSubscription
|> where([s], s.stripe_connect_plan_id == ^project.stripe_connect_plan.id and s.user_id == ^user.id)
|> Repo.one
|> Repo.get_by(stripe_connect_plan_id: plan.id, user_id: user.id)
end

defp create(%Project{} = project, %User{} = user, attributes) do
defp create(project, user, attributes) do
with platform_card <- user.stripe_platform_card,
platform_customer <- user.stripe_platform_customer,
connect_account <- project.organization.stripe_connect_account,
plan <- project.stripe_connect_plan,
{:ok, connect_customer} <- StripeConnectCustomerService.find_or_create(platform_customer, connect_account, user),
{:ok, connect_card} <- StripeConnectCardService.find_or_create(platform_card, connect_customer, platform_customer, connect_account),
create_attributes <- to_create_attributes(connect_card, connect_customer, plan, attributes),
create_attributes <- api_create_attributes(connect_card, connect_customer, plan, attributes),
{:ok, subscription} <- @api.Subscription.create(create_attributes, connect_account: connect_account.id_from_stripe),
insert_attributes <- to_insert_attributes(attributes, plan),
insert_attributes <- local_insert_attributes(attributes, plan),
{:ok, params} <- StripeConnectSubscriptionAdapter.to_params(subscription, insert_attributes),
{:ok, %StripeConnectSubscription{} = stripe_connect_subscription} <- insert_subscription(params)
do
Expand All @@ -108,21 +108,22 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
end
end

defp get_project(%StripeConnectSubscription{stripe_connect_plan_id: stripe_connect_plan_id}) do
%StripeConnectPlan{project_id: project_id} = Repo.get(StripeConnectPlan, stripe_connect_plan_id)
{:ok, get_project(project_id, [:stripe_connect_plan])}
end
defp get_project_with_preloads(id) do
preloads = [:stripe_connect_plan, [organization: :stripe_connect_account]]

@default_project_preloads [:stripe_connect_plan, [{:organization, :stripe_connect_account}]]

defp get_project(project_id, preloads \\ @default_project_preloads) do
Repo.get(Project, project_id) |> Repo.preload(preloads)
case Repo.get(Project, id) |> Repo.preload(preloads) do
nil -> {:error, :project_not_found}
project -> {:ok, project}
end
end

@default_user_preloads [:stripe_platform_customer, [{:stripe_platform_card, :stripe_connect_cards}]]
defp get_user_with_preloads(user_id) do
preloads = [:stripe_platform_customer, [{:stripe_platform_card, :stripe_connect_cards}]]

defp get_user(user_id, preloads \\ @default_user_preloads) do
Repo.get(User, user_id) |> Repo.preload(preloads)
case Repo.get(User, user_id) |> Repo.preload(preloads) do
nil -> {:error, :user_not_found}
user -> {:ok, user}
end
end

defp insert_subscription(params) do
Expand All @@ -131,7 +132,7 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
|> Repo.insert
end

defp to_create_attributes(card, customer, plan, %{"quantity" => quantity}) do
defp api_create_attributes(card, customer, plan, %{"quantity" => quantity}) do
%{
application_fee_percent: 5,
customer: customer.id_from_stripe,
Expand All @@ -141,10 +142,12 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
}
end

defp to_insert_attributes(attrs, %StripeConnectPlan{id: stripe_connect_plan_id}) do
defp local_insert_attributes(attrs, %StripeConnectPlan{id: stripe_connect_plan_id}) do
attrs |> Map.merge(%{"stripe_connect_plan_id" => stripe_connect_plan_id})
end

# update_from_stripe

defp retrieve_connect_account(connect_customer_id) do
customer =
StripeConnectCustomer
Expand All @@ -155,7 +158,10 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
end

defp load_subscription(id_from_stripe) do
subscription = Repo.get_by(StripeConnectSubscription, id_from_stripe: id_from_stripe)
subscription =
StripeConnectSubscription
|> Repo.get_by(id_from_stripe: id_from_stripe)
|> Repo.preload([stripe_connect_plan: [project: [:stripe_connect_plan, :donation_goals]]])

{:ok, subscription}
end
Expand Down
25 changes: 25 additions & 0 deletions test/support/view_case.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule CodeCorps.ViewCase do
@moduledoc """
This module defines the test case to be used by
tests for views defined in the application.
"""

use ExUnit.CaseTemplate

using do
quote do
import CodeCorps.Factories
import Phoenix.View, only: [render: 3]
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
4 changes: 1 addition & 3 deletions test/views/category_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.CategoryViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
category = insert(:category)
Expand Down
4 changes: 1 addition & 3 deletions test/views/changeset_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
defmodule CodeCorps.ChangesetViewTest do
use CodeCorps.ConnCase, async: true
use CodeCorps.ViewCase

alias CodeCorps.Preview

import Phoenix.View, only: [render: 3]

test "renders all errors properly" do
changeset = Preview.create_changeset(%Preview{}, %{})

Expand Down
4 changes: 1 addition & 3 deletions test/views/comment_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.CommentViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
task = insert(:task)
Expand Down
4 changes: 1 addition & 3 deletions test/views/donation_goal_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.DonationGoalViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
project = insert(:project)
Expand Down
2 changes: 1 addition & 1 deletion test/views/error_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule CodeCorps.ErrorViewTest do
use CodeCorps.ConnCase, async: true
use CodeCorps.ViewCase

# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
Expand Down
2 changes: 1 addition & 1 deletion test/views/layout_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
defmodule CodeCorps.LayoutViewTest do
use CodeCorps.ConnCase, async: true
use CodeCorps.ViewCase
end
4 changes: 1 addition & 3 deletions test/views/organization_membership_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.OrganizationMembershipViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
organization = insert(:organization)
Expand Down
4 changes: 1 addition & 3 deletions test/views/organization_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.OrganizationViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
organization = insert(:organization)
Expand Down
2 changes: 1 addition & 1 deletion test/views/page_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
defmodule CodeCorps.PageViewTest do
use CodeCorps.ConnCase, async: true
use CodeCorps.ViewCase
end
4 changes: 1 addition & 3 deletions test/views/preview_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.PreviewViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
user = insert(:user)
Expand Down
4 changes: 1 addition & 3 deletions test/views/project_category_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.ProjectCategoryViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
project_category = insert(:project_category)
Expand Down
4 changes: 1 addition & 3 deletions test/views/project_skill_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.ProjectSkillViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
project_skill = insert(:project_skill)
Expand Down
4 changes: 1 addition & 3 deletions test/views/project_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.ProjectViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
organization = insert(:organization)
Expand Down
4 changes: 1 addition & 3 deletions test/views/role_skill_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.RoleSkillViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
role_skill = insert(:role_skill)
Expand Down
4 changes: 1 addition & 3 deletions test/views/role_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.RoleViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
role = insert(:role)
Expand Down
4 changes: 1 addition & 3 deletions test/views/skill_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.SkillViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
skill = insert(:skill)
Expand Down
4 changes: 1 addition & 3 deletions test/views/slugged_route_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.SluggedRouteViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly for organization" do
organization = insert(:organization)
Expand Down
6 changes: 1 addition & 5 deletions test/views/stripe_connect_account_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
defmodule CodeCorps.StripeConnectAccountViewTest do
@moduledoc false

use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
organization = insert(:organization)
Expand Down
4 changes: 1 addition & 3 deletions test/views/stripe_connect_plan_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defmodule CodeCorps.StripeConnectPlanViewTest do
use CodeCorps.ConnCase, async: true

import Phoenix.View, only: [render: 3]
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
project = insert(:project)
Expand Down
37 changes: 37 additions & 0 deletions test/views/stripe_connect_subscription_view_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule CodeCorps.StripeConnectSubscriptionViewTest do
use CodeCorps.ViewCase

test "renders all attributes and relationships properly" do
project = insert(:project)
plan = insert(:stripe_connect_plan, project: project)
user = insert(:user)
subscription = insert(:stripe_connect_subscription, stripe_connect_plan: plan, user: user)

rendered_json = render(CodeCorps.StripeConnectSubscriptionView, "show.json-api", data: subscription)

expected_json = %{
"data" => %{
"attributes" => %{
"inserted-at" => subscription.inserted_at,
"quantity" => subscription.quantity,
"updated-at" => subscription.updated_at
},
"id" => subscription.id |> Integer.to_string,
"relationships" => %{
"project" => %{
"data" => %{"id" => project.id |> Integer.to_string, "type" => "project"}
},
"user" => %{
"data" => %{"id" => user.id |> Integer.to_string, "type" => "user"}
}
},
"type" => "stripe-connect-subscription",
},
"jsonapi" => %{
"version" => "1.0"
}
}

assert rendered_json == expected_json
end
end
Loading

0 comments on commit 575134d

Please sign in to comment.