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
61 changes: 61 additions & 0 deletions lib/code_corps/stripe_service/adapters/stripe_event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule CodeCorps.StripeService.Adapters.StripeEventAdapter do
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]},
{:type, [:type]},
{:user_id, [:user_id]}
]

@doc """
Transforms a `%Stripe.Event{}` and a set of local attributes into a
map of parameters used to create or update a `StripeEvent` record.
"""
def to_params(%Stripe.Event{} = stripe_event, %{} = attributes) do
result =
stripe_event
|> Map.from_struct
|> transform_map(@stripe_mapping)
|> add_non_stripe_attributes(attributes)
|> add_object_type(stripe_event)
|> add_object_id(stripe_event)
|> keys_to_string

{:ok, result}
end

# Names of attributes which we need to store localy,
# but are not part of the Stripe API record
@non_stripe_attributes ["endpoint", "status"]

defp add_non_stripe_attributes(%{} = params, %{} = attributes) do
attributes
|> get_non_stripe_attributes
|> add_to(params)
end

defp get_non_stripe_attributes(%{} = attributes) do
attributes |> Map.take(@non_stripe_attributes)
end

defp add_to(%{} = attributes, %{} = params) do
params |> Map.merge(attributes)
end

defp add_object_type(params, stripe_event) do
object_type =
stripe_event.data.object.__struct__
|> Module.split
|> List.last
|> Inflex.underscore

params |> Map.put(:object_type, object_type)
end

defp add_object_id(params, stripe_event) do
params |> Map.put(:object_id, stripe_event.data.object.id)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
alias CodeCorps.StripeEvent
alias CodeCorps.Repo
alias CodeCorps.StripeService.WebhookProcessing.{ConnectEventHandler, PlatformEventHandler}
alias CodeCorps.StripeService.Adapters.StripeEventAdapter

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

Expand Down Expand Up @@ -47,9 +48,9 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
end

defp do_process(id, user_id, handler, json) do
with {:ok, %Stripe.Event{id: api_event_id, type: api_event_type, user_id: api_user_id}} <- retrieve_event_from_api(id, user_id),
with {:ok, %Stripe.Event{} = event} <- retrieve_event_from_api(id, user_id),
{:ok, endpoint} <- infer_endpoint_from_handler(handler),
{:ok, %StripeEvent{} = event} <- find_or_create_event(api_event_id, api_event_type, api_user_id, endpoint)
{:ok, %StripeEvent{} = event} <- find_or_create_event(event, endpoint)
do
handle_event(json, event, handler)
else
Expand All @@ -64,11 +65,11 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
end
end

defp find_or_create_event(id_from_stripe, type, user_id, endpoint) do
case find_event(id_from_stripe) do
defp find_or_create_event(%Stripe.Event{} = event, endpoint) do
case find_event(event.id) do
%StripeEvent{status: "processing"} -> {:error, :already_processing}
%StripeEvent{} = event -> {:ok, event}
nil -> create_event(id_from_stripe, endpoint, type, user_id)
nil -> create_event(event, endpoint)
end
end

Expand All @@ -86,13 +87,14 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do

defp infer_endpoint_from_handler(ConnectEventHandler), do: {:ok, "connect"}
defp infer_endpoint_from_handler(PlatformEventHandler), do: {:ok, "platform"}
defp infer_endpoint_from_handler(_), do: {:error, :invalid_handler}

defp retrieve_event_from_api(id, nil), do: @api.Event.retrieve(id)
defp retrieve_event_from_api(id, user_id), do: @api.Event.retrieve(id, connect_account: user_id)

defp create_event(id_from_stripe, endpoint, type, user_id) do
%StripeEvent{} |> StripeEvent.create_changeset(%{endpoint: endpoint, id_from_stripe: id_from_stripe, type: type, user_id: user_id}) |> Repo.insert
defp create_event(%Stripe.Event{} = event, endpoint) do
with {:ok, params} <- StripeEventAdapter.to_params(event, %{"endpoint" => endpoint}) do
%StripeEvent{} |> StripeEvent.create_changeset(params) |> Repo.insert
end
end

defp set_errored(%StripeEvent{} = event) do
Expand Down
11 changes: 10 additions & 1 deletion lib/code_corps/stripe_testing/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ defmodule CodeCorps.StripeTesting.Event do
end

defp do_retrieve(_) do

%Stripe.Event{
api_version: "2016-07-06",
created: 1479472835,
id: "evt_123",
data: %{
object: %Stripe.Customer{
id: "cus_123"
}
},
livemode: false,
object: "event",
pending_webhooks: 1,
Expand All @@ -25,6 +29,11 @@ defmodule CodeCorps.StripeTesting.Event do
api_version: "2016-07-06",
created: 1479472835,
id: "evt_123",
data: %{
object: %Stripe.Customer{
id: "cus_123"
}
},
livemode: false,
object: "event",
pending_webhooks: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule CodeCorps.Repo.Migrations.AddObjectIdTypeToEvents do
use Ecto.Migration

def change do
alter table(:stripe_events) do
add :object_id, :string, null: false
add :object_type, :string, null: false
end
end
end
6 changes: 5 additions & 1 deletion test/controllers/stripe_connect_events_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ defmodule CodeCorps.StripeConnectEventsControllerTest do

wait_for_supervisor

assert StripeEvent |> Repo.aggregate(:count, :id) == 1
event = StripeEvent |> Repo.one
{:ok, mock_api_event} = CodeCorps.StripeTesting.Event.retrieve("evt_123")

assert event.object_id == mock_api_event.data.object.id
assert event.object_type == "customer"
end

test "uses existing event if id exists", %{conn: conn} do
Expand Down
43 changes: 43 additions & 0 deletions test/lib/code_corps/stripe_service/adapters/stripe_event_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule CodeCorps.StripeService.Adapters.StripeEventTest do
use CodeCorps.ModelCase

import CodeCorps.StripeService.Adapters.StripeEventAdapter, only: [to_params: 2]

@stripe_event %Stripe.Event{
api_version: nil,
created: nil,
data: %{
object: %Stripe.Customer{
id: "cus_123"
}
},
id: "evt_123",
livemode: false,
object: "event",
pending_webhooks: nil,
request: nil,
type: "some.event",
user_id: "act_123"
}

@attributes %{
"endpoint" => "connect"
}

@local_map %{
"endpoint" => "connect",
"id_from_stripe" => "evt_123",
"object_id" => "cus_123",
"object_type" => "customer",
"type" => "some.event",
"user_id" => "act_123"
}

describe "to_params/2" do
test "converts from stripe map to local properly" do

{:ok, result} = to_params(@stripe_event, @attributes)
assert result == @local_map
end
end
end
19 changes: 14 additions & 5 deletions test/models/stripe_event_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@ defmodule CodeCorps.StripeEventTest do
alias CodeCorps.StripeEvent

describe "create_changeset/2" do
@valid_attrs %{endpoint: "connect", id_from_stripe: "evt_123", type: "any.event"}
@valid_attrs %{
endpoint: "connect",
id_from_stripe: "evt_123",
object_id: "cus_123",
object_type: "customer",
type: "any.event"
}

test "reports as valid when attributes are valid" do
changeset = StripeEvent.create_changeset(%StripeEvent{}, @valid_attrs)
assert changeset.valid?
end

test "requires :id_from_stripe, :type" do
test "required params" do
changeset = StripeEvent.create_changeset(%StripeEvent{}, %{})

refute changeset.valid?
assert_error_message(changeset, :endpoint, "can't be blank")
assert_error_message(changeset, :id_from_stripe, "can't be blank")
assert_error_message(changeset, :type, "can't be blank")

assert_validation_triggered(changeset, :endpoint, :required)
assert_validation_triggered(changeset, :id_from_stripe, :required)
assert_validation_triggered(changeset, :object_id, :required)
assert_validation_triggered(changeset, :object_type, :required)
assert_validation_triggered(changeset, :type, :required)
end

test "sets :status to 'processing'" do
Expand Down
2 changes: 2 additions & 0 deletions test/support/factories.ex
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ defmodule CodeCorps.Factories do
%CodeCorps.StripeEvent{
endpoint: sequence(:endpoint, fn(_) -> Enum.random(~w{ connect platform }) end),
id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"),
object_id: "cus_123",
object_type: "customer",
status: sequence(:status, fn(_) -> Enum.random(~w{ unprocessed processed errored }) end),
type: "test.type"
}
Expand Down
6 changes: 4 additions & 2 deletions web/models/stripe_event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ defmodule CodeCorps.StripeEvent do
schema "stripe_events" do
field :endpoint, :string, null: false
field :id_from_stripe, :string, null: false
field :object_id, :string
field :object_type, :string
field :status, :string, default: "unprocessed"
field :type, :string, null: false
field :user_id, :string
Expand All @@ -37,8 +39,8 @@ defmodule CodeCorps.StripeEvent do
"""
def create_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:endpoint, :id_from_stripe, :type, :user_id])
|> validate_required([:endpoint, :id_from_stripe, :type])
|> cast(params, [:endpoint, :id_from_stripe, :object_id, :object_type, :type, :user_id])
|> validate_required([:endpoint, :id_from_stripe, :object_id, :object_type, :type])
|> put_change(:status, "processing")
|> validate_inclusion(:status, states)
|> validate_inclusion(:endpoint, endpoints)
Expand Down