Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions lib/code_corps/analytics/in_memory_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ defmodule CodeCorps.Analytics.InMemoryAPI do
Each function should have the same signature as `CodeCorps.Analytics.SegmentAPI` and simply return `nil`.
"""

def identify(_user_id, _traits), do: nil
def track(_user_id, _event_name, _properties), do: nil
require Logger

def identify(user_id, _traits), do: log_identify(user_id)

def track(user_id, event_name, _properties), do: log_track(user_id, event_name)

defp log_identify(user_id) do
Logger.info "Called identify for User #{user_id}"
end

defp log_track(user_id, event_name) do
Logger.info "Called track for event #{event_name} for User #{user_id}"
end
end
38 changes: 24 additions & 14 deletions lib/code_corps/analytics/segment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@ defmodule CodeCorps.Analytics.Segment do
```
"""

alias CodeCorps.Comment
alias CodeCorps.OrganizationMembership
alias CodeCorps.Task
alias CodeCorps.User
alias CodeCorps.UserCategory
alias CodeCorps.UserRole
alias CodeCorps.UserSkill
alias CodeCorps.{Comment, OrganizationMembership, StripeInvoice, Task, User, UserCategory, UserRole, UserSkill}
alias Ecto.Changeset

@api Application.get_env(:code_corps, :analytics)
Expand All @@ -37,6 +31,7 @@ defmodule CodeCorps.Analytics.Segment do
end
def get_event_name(:created, %OrganizationMembership{}), do: "Requested Organization Membership"
def get_event_name(:edited, %OrganizationMembership{}), do: "Approved Organization Membership"
def get_event_name(:payment_succeeded, %StripeInvoice{}), do: "Processed Subscription Payment"
def get_event_name(:created, %UserCategory{}), do: "Added User Category"
def get_event_name(:created, %UserSkill{}), do: "Added User Skill"
def get_event_name(:created, %UserRole{}), do: "Added User Role"
Expand All @@ -61,13 +56,16 @@ defmodule CodeCorps.Analytics.Segment do
def track({:ok, record}, action, %Plug.Conn{} = conn) when action in @actions_without_properties do
action_name = get_event_name(action, record)
do_track(conn, action_name)

{:ok, record}
end
def track({:ok, record}, action, %Plug.Conn{} = conn) do
action_name = get_event_name(action, record)
do_track(conn, action_name, properties(record))

{:ok, record}
end
def track({:ok, %{user_id: user_id} = record}, action, nil) do
action_name = get_event_name(action, record)
do_track(user_id, action_name, properties(record))
{:ok, record}
end
def track({:error, %Changeset{} = changeset}, _action, _conn), do: {:error, changeset}
Expand Down Expand Up @@ -98,14 +96,13 @@ defmodule CodeCorps.Analytics.Segment do
|> Enum.join(" ")
end

defp do_track(conn, event_name, properties) do
defp do_track(conn_or_user, event_name, properties \\ %{})
defp do_track(%Plug.Conn{} = conn, event_name, properties) do
@api.track(conn.assigns[:current_user].id, event_name, properties)
conn
end

defp do_track(conn, event_name) do
@api.track(conn.assigns[:current_user].id, event_name, %{})
conn
defp do_track(user_id, event_name, properties) do
@api.track(user_id, event_name, properties)
end

defp properties(comment = %Comment{}) do
Expand All @@ -125,6 +122,17 @@ defmodule CodeCorps.Analytics.Segment do
organization_id: organization_membership.organization.id
}
end
defp properties(invoice = %StripeInvoice{}) do
revenue = invoice.total / 100 # TODO: this only works for some currencies
currency = String.capitalize(invoice.currency) # ISO 4127 format

%{
currency: currency,
invoice_id: invoice.id,
revenue: revenue,
user_id: invoice.user_id
}
end
defp properties(task = %Task{}) do
%{
task: task.title,
Expand Down Expand Up @@ -154,10 +162,12 @@ defmodule CodeCorps.Analytics.Segment do
skill_id: user_skill.skill.id
}
end

defp properties(_struct) do
%{}
end


defp traits(user) do
%{
admin: user.admin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectCustomerAdapter do
{:ok, result}
end

@non_stripe_attributes ["stripe_connect_account_id", "stripe_platform_customer_id"]
@non_stripe_attributes ["stripe_connect_account_id", "stripe_platform_customer_id", "user_id"]

defp add_non_stripe_attributes(%{} = params, %{} = attributes) do
attributes
Expand Down
68 changes: 68 additions & 0 deletions lib/code_corps/stripe_service/adapters/stripe_invoice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule CodeCorps.StripeService.Adapters.StripeInvoiceAdapter do
alias CodeCorps.{Repo, StripeConnectCustomer, StripeConnectSubscription}

import CodeCorps.MapUtils, only: [keys_to_string: 1]
import CodeCorps.StripeService.Util, only: [transform_map: 2]

# Mapping of stripe record attributes to locally stored attributes
# Format is {:local_key, [:nesting, :of, :stripe, :keys]}
@stripe_mapping [
{:id_from_stripe, [:id]},
{:amount_due, [:amount_due]},
{:application_fee, [:application_fee]},
{:attempt_count, [:attempt_count]},
{:attempted, [:attempted]},
{:charge_id_from_stripe, [:charge]},
{:closed, [:closed]},
{:currency, [:currency]},
{:customer_id_from_stripe, [:customer]},
{:date, [:date]},
{:description, [:description]},
{:ending_balance, [:ending_balance]},
{:forgiven, [:forgiven]},
{:next_payment_attempt, [:next_payment_attempt]},
{:paid, [:paid]},
{:period_end, [:period_end]},
{:period_start, [:period_start]},
{:receipt_number, [:receipt_number]},
{:starting_balance, [:starting_balance]},
{:statement_descriptor, [:statement_descriptor]},
{:subscription_id_from_stripe, [:subscription]},
{:subscription_proration_date, [:subscription_proration_date]},
{:subtotal, [:subtotal]},
{:tax, [:tax]},
{:tax_percent, [:tax_percent]},
{:total, [:total]},
{:webhooks_delivered_at, [:webhooks_delivered_at]},
]

@doc """
Transforms a `%Stripe.Invoice{}` and a set of local attributes into a
map of parameters used to create or update a `StripeInvoice` record.
"""
def to_params(%Stripe.Invoice{} = stripe_invoice) do
result =
stripe_invoice
|> Map.from_struct
|> transform_map(@stripe_mapping)
|> add_stripe_connect_subscription_id
|> add_user_id
|> keys_to_string

{:ok, result}
end

defp add_stripe_connect_subscription_id(%{subscription_id_from_stripe: subscription_id_from_stripe} = map) do
%StripeConnectSubscription{id: id} =
StripeConnectSubscription
|> Repo.get_by(id_from_stripe: subscription_id_from_stripe)
Map.put(map, :stripe_connect_subscription_id, id)
end

defp add_user_id(%{customer_id_from_stripe: customer_id_from_stripe} = map) do
%StripeConnectCustomer{user_id: user_id} =
StripeConnectCustomer
|> Repo.get_by(id_from_stripe: customer_id_from_stripe)
Map.put(map, :user_id, user_id)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule CodeCorps.StripeService.Events.InvoicePaymentSucceeded do
def handle(%{"data" => %{"object" => %{"id" => id_from_stripe, "customer" => customer_id_from_stripe}}}) do
CodeCorps.StripeService.StripeInvoiceService.create(id_from_stripe, customer_id_from_stripe)
end
end
16 changes: 7 additions & 9 deletions lib/code_corps/stripe_service/stripe_connect_customer.ex
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
defmodule CodeCorps.StripeService.StripeConnectCustomerService do
alias CodeCorps.Repo
alias CodeCorps.StripeService.Adapters.StripeConnectCustomerAdapter
alias CodeCorps.StripeConnectAccount
alias CodeCorps.StripeConnectCustomer
alias CodeCorps.StripePlatformCustomer
alias CodeCorps.{Repo, StripeConnectAccount, StripeConnectCustomer, StripePlatformCustomer, User}

import CodeCorps.MapUtils, only: [rename: 3, keys_to_string: 1]
import Ecto.Query # needed for match

@api Application.get_env(:code_corps, :stripe)

def find_or_create(%StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account) do
def find_or_create(%StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account, %User{} = user) do
case get_from_db(connect_account.id, platform_customer.id) do
%StripeConnectCustomer{} = existing_customer ->
{:ok, existing_customer}
nil ->
create(platform_customer, connect_account)
create(platform_customer, connect_account, user)
end
end

def update(%StripeConnectCustomer{id_from_stripe: id_from_stripe, stripe_connect_account: connect_account}, attributes) do
@api.Customer.update(id_from_stripe, attributes, connect_account: connect_account.id_from_stripe)
end

defp create(%StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account) do
attributes = platform_customer |> create_non_stripe_attributes(connect_account)
defp create(%StripePlatformCustomer{} = platform_customer, %StripeConnectAccount{} = connect_account, %User{} = user) do
attributes = create_non_stripe_attributes(platform_customer, connect_account, user)
stripe_attributes = create_stripe_attributes(platform_customer)

with {:ok, customer} <-
Expand All @@ -45,12 +42,13 @@ defmodule CodeCorps.StripeService.StripeConnectCustomerService do
|> Repo.one
end

defp create_non_stripe_attributes(platform_customer, connect_account) do
defp create_non_stripe_attributes(platform_customer, connect_account, user) do
platform_customer
|> Map.from_struct
|> Map.take([:id])
|> rename(:id, :stripe_platform_customer_id)
|> Map.put(:stripe_connect_account_id, connect_account.id)
|> Map.put(:user_id, user.id)
|> keys_to_string
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do
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),
{: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),
{:ok, subscription} <- @api.Subscription.create(create_attributes, connect_account: connect_account.id_from_stripe),
Expand Down
28 changes: 28 additions & 0 deletions lib/code_corps/stripe_service/stripe_invoice_service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule CodeCorps.StripeService.StripeInvoiceService do

alias CodeCorps.{Repo, StripeConnectAccount, StripeConnectCustomer, StripeInvoice}
alias CodeCorps.StripeService.Adapters.StripeInvoiceAdapter

@api Application.get_env(:code_corps, :stripe)

@spec create(binary, binary) :: {:ok, %StripeInvoice{}} | {:error, %Ecto.Changeset{}}
def create(invoice_id_from_stripe, customer_id_from_stripe) do
with account_id <- get_connect_account(customer_id_from_stripe),
{:ok, %Stripe.Invoice{} = invoice} <- @api.Invoice.retrieve(invoice_id_from_stripe, connect_account: account_id),
{:ok, params} <- StripeInvoiceAdapter.to_params(invoice)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some weird indentation issue here.

do
%StripeInvoice{}
|> StripeInvoice.create_changeset(params)
|> Repo.insert
|> CodeCorps.Analytics.Segment.track(:payment_succeeded, nil)
end
end

defp get_connect_account(customer_id_from_stripe) do
%StripeConnectCustomer{stripe_connect_account: %StripeConnectAccount{id_from_stripe: stripe_connect_account_id}} =
StripeConnectCustomer
|> Repo.get_by(id_from_stripe: customer_id_from_stripe)
|> Repo.preload([:stripe_connect_account])
stripe_connect_account_id
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ defmodule CodeCorps.StripeService.WebhookProcessing.ConnectEventHandler do
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)
defp do_handle(_, _), do: {:ok, :unhandled_event}
end
40 changes: 40 additions & 0 deletions lib/code_corps/stripe_testing/invoice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule CodeCorps.StripeTesting.Invoice do
def retrieve(id, _) do
{:ok, invoice(id)}
end

defp invoice(id) do
%Stripe.Invoice{
amount_due: 1000,
application_fee: 50,
attempt_count: 1,
attempted: true,
charge: "ch_123",
closed: true,
currency: "usd",
customer: "cus_123",
date: 1_483_553_506,
description: nil,
discount: nil,
ending_balance: 0,
forgiven: false,
id: id,
livemode: false,
metadata: %{},
next_payment_attempt: nil,
paid: true,
period_end: 1_483_553_506,
period_start: 1_483_553_506,
receipt_number: nil,
starting_balance: 0,
statement_descriptor: nil,
subscription: "sub_123",
subscription_proration_date: nil,
subtotal: 1000,
tax: nil,
tax_percent: nil,
total: 1000,
webhooks_delivered_at: 1_483_553_511
}
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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", "f6ddf082a86f558e23789f24aea581add3ca8821", [branch: "2.0"]},
"stripity_stripe": {:git, "https://github.com/code-corps/stripity_stripe.git", "f29a3308136594f976d8e165100385628e2eece2", [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]}]},
Expand Down
42 changes: 42 additions & 0 deletions priv/repo/migrations/20170104212623_add_invoices.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule CodeCorps.Repo.Migrations.AddInvoices do
use Ecto.Migration

def change do
create table(:stripe_invoices) do
add :amount_due, :integer
add :application_fee, :integer
add :attempt_count, :integer
add :attempted, :boolean
add :charge_id_from_stripe, :string, null: false
add :closed, :boolean
add :currency, :string
add :customer_id_from_stripe, :string, null: false
add :date, :integer
add :description, :string
add :ending_balance, :integer
add :forgiven, :boolean
add :id_from_stripe, :string, null: false
add :next_payment_attempt, :integer
add :paid, :boolean
add :period_end, :integer
add :period_start, :integer
add :receipt_number, :string
add :starting_balance, :integer
add :statement_descriptor, :string
add :subscription_id_from_stripe, :string, null: false
add :subscription_proration_date, :integer
add :subtotal, :integer
add :tax, :integer
add :tax_percent, :float
add :total, :integer
add :webhooks_delievered_at, :integer

add :stripe_connect_subscription_id, references(:stripe_connect_subscriptions), null: false
add :user_id, references(:users), null: false

timestamps()
end

create index(:stripe_invoices, [:id_from_stripe], unique: true)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule CodeCorps.Repo.Migrations.AddUserToStripeConnectCustomer do
use Ecto.Migration

def change do
alter table(:stripe_connect_customers) do
add :user_id, references(:users), null: false
end
end
end
Loading