Skip to content

Commit 08dcf7d

Browse files
authored
Merge pull request #633 from code-corps/617-track-event-object-ids
Store object ids with events
2 parents 81ace6a + 9bbe954 commit 08dcf7d

9 files changed

Lines changed: 159 additions & 17 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
defmodule CodeCorps.StripeService.Adapters.StripeEventAdapter do
2+
import CodeCorps.MapUtils, only: [keys_to_string: 1]
3+
import CodeCorps.StripeService.Util, only: [transform_map: 2]
4+
5+
# Mapping of stripe record attributes to locally stored attributes
6+
# Format is {:local_key, [:nesting, :of, :stripe, :keys]}
7+
@stripe_mapping [
8+
{:id_from_stripe, [:id]},
9+
{:type, [:type]},
10+
{:user_id, [:user_id]}
11+
]
12+
13+
@doc """
14+
Transforms a `%Stripe.Event{}` and a set of local attributes into a
15+
map of parameters used to create or update a `StripeEvent` record.
16+
"""
17+
def to_params(%Stripe.Event{} = stripe_event, %{} = attributes) do
18+
result =
19+
stripe_event
20+
|> Map.from_struct
21+
|> transform_map(@stripe_mapping)
22+
|> add_non_stripe_attributes(attributes)
23+
|> add_object_type(stripe_event)
24+
|> add_object_id(stripe_event)
25+
|> keys_to_string
26+
27+
{:ok, result}
28+
end
29+
30+
# Names of attributes which we need to store localy,
31+
# but are not part of the Stripe API record
32+
@non_stripe_attributes ["endpoint", "status"]
33+
34+
defp add_non_stripe_attributes(%{} = params, %{} = attributes) do
35+
attributes
36+
|> get_non_stripe_attributes
37+
|> add_to(params)
38+
end
39+
40+
defp get_non_stripe_attributes(%{} = attributes) do
41+
attributes |> Map.take(@non_stripe_attributes)
42+
end
43+
44+
defp add_to(%{} = attributes, %{} = params) do
45+
params |> Map.merge(attributes)
46+
end
47+
48+
defp add_object_type(params, stripe_event) do
49+
object_type =
50+
stripe_event.data.object.__struct__
51+
|> Module.split
52+
|> List.last
53+
|> Inflex.underscore
54+
55+
params |> Map.put(:object_type, object_type)
56+
end
57+
58+
defp add_object_id(params, stripe_event) do
59+
params |> Map.put(:object_id, stripe_event.data.object.id)
60+
end
61+
end

lib/code_corps/stripe_service/webhook_processing/webhook_processor.ex

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
66
alias CodeCorps.StripeEvent
77
alias CodeCorps.Repo
88
alias CodeCorps.StripeService.WebhookProcessing.{ConnectEventHandler, PlatformEventHandler}
9+
alias CodeCorps.StripeService.Adapters.StripeEventAdapter
910

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

@@ -47,9 +48,9 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
4748
end
4849

4950
defp do_process(id, user_id, handler, json) do
50-
with {:ok, %Stripe.Event{id: api_event_id, type: api_event_type, user_id: api_user_id}} <- retrieve_event_from_api(id, user_id),
51+
with {:ok, %Stripe.Event{} = event} <- retrieve_event_from_api(id, user_id),
5152
{:ok, endpoint} <- infer_endpoint_from_handler(handler),
52-
{:ok, %StripeEvent{} = event} <- find_or_create_event(api_event_id, api_event_type, api_user_id, endpoint)
53+
{:ok, %StripeEvent{} = event} <- find_or_create_event(event, endpoint)
5354
do
5455
handle_event(json, event, handler)
5556
else
@@ -64,11 +65,11 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
6465
end
6566
end
6667

67-
defp find_or_create_event(id_from_stripe, type, user_id, endpoint) do
68-
case find_event(id_from_stripe) do
68+
defp find_or_create_event(%Stripe.Event{} = event, endpoint) do
69+
case find_event(event.id) do
6970
%StripeEvent{status: "processing"} -> {:error, :already_processing}
7071
%StripeEvent{} = event -> {:ok, event}
71-
nil -> create_event(id_from_stripe, endpoint, type, user_id)
72+
nil -> create_event(event, endpoint)
7273
end
7374
end
7475

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

8788
defp infer_endpoint_from_handler(ConnectEventHandler), do: {:ok, "connect"}
8889
defp infer_endpoint_from_handler(PlatformEventHandler), do: {:ok, "platform"}
89-
defp infer_endpoint_from_handler(_), do: {:error, :invalid_handler}
9090

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

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

98100
defp set_errored(%StripeEvent{} = event) do

lib/code_corps/stripe_testing/event.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ defmodule CodeCorps.StripeTesting.Event do
77
end
88

99
defp do_retrieve(_) do
10-
1110
%Stripe.Event{
1211
api_version: "2016-07-06",
1312
created: 1479472835,
1413
id: "evt_123",
14+
data: %{
15+
object: %Stripe.Customer{
16+
id: "cus_123"
17+
}
18+
},
1519
livemode: false,
1620
object: "event",
1721
pending_webhooks: 1,
@@ -25,6 +29,11 @@ defmodule CodeCorps.StripeTesting.Event do
2529
api_version: "2016-07-06",
2630
created: 1479472835,
2731
id: "evt_123",
32+
data: %{
33+
object: %Stripe.Customer{
34+
id: "cus_123"
35+
}
36+
},
2837
livemode: false,
2938
object: "event",
3039
pending_webhooks: 1,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule CodeCorps.Repo.Migrations.AddObjectIdTypeToEvents do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:stripe_events) do
6+
add :object_id, :string, null: false
7+
add :object_type, :string, null: false
8+
end
9+
end
10+
end

test/controllers/stripe_connect_events_controller_test.exs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ defmodule CodeCorps.StripeConnectEventsControllerTest do
107107

108108
wait_for_supervisor
109109

110-
assert StripeEvent |> Repo.aggregate(:count, :id) == 1
110+
event = StripeEvent |> Repo.one
111+
{:ok, mock_api_event} = CodeCorps.StripeTesting.Event.retrieve("evt_123")
112+
113+
assert event.object_id == mock_api_event.data.object.id
114+
assert event.object_type == "customer"
111115
end
112116

113117
test "uses existing event if id exists", %{conn: conn} do
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
defmodule CodeCorps.StripeService.Adapters.StripeEventTest do
2+
use CodeCorps.ModelCase
3+
4+
import CodeCorps.StripeService.Adapters.StripeEventAdapter, only: [to_params: 2]
5+
6+
@stripe_event %Stripe.Event{
7+
api_version: nil,
8+
created: nil,
9+
data: %{
10+
object: %Stripe.Customer{
11+
id: "cus_123"
12+
}
13+
},
14+
id: "evt_123",
15+
livemode: false,
16+
object: "event",
17+
pending_webhooks: nil,
18+
request: nil,
19+
type: "some.event",
20+
user_id: "act_123"
21+
}
22+
23+
@attributes %{
24+
"endpoint" => "connect"
25+
}
26+
27+
@local_map %{
28+
"endpoint" => "connect",
29+
"id_from_stripe" => "evt_123",
30+
"object_id" => "cus_123",
31+
"object_type" => "customer",
32+
"type" => "some.event",
33+
"user_id" => "act_123"
34+
}
35+
36+
describe "to_params/2" do
37+
test "converts from stripe map to local properly" do
38+
39+
{:ok, result} = to_params(@stripe_event, @attributes)
40+
assert result == @local_map
41+
end
42+
end
43+
end

test/models/stripe_event_test.exs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,29 @@ defmodule CodeCorps.StripeEventTest do
44
alias CodeCorps.StripeEvent
55

66
describe "create_changeset/2" do
7-
@valid_attrs %{endpoint: "connect", id_from_stripe: "evt_123", type: "any.event"}
7+
@valid_attrs %{
8+
endpoint: "connect",
9+
id_from_stripe: "evt_123",
10+
object_id: "cus_123",
11+
object_type: "customer",
12+
type: "any.event"
13+
}
814

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

14-
test "requires :id_from_stripe, :type" do
20+
test "required params" do
1521
changeset = StripeEvent.create_changeset(%StripeEvent{}, %{})
1622

1723
refute changeset.valid?
18-
assert_error_message(changeset, :endpoint, "can't be blank")
19-
assert_error_message(changeset, :id_from_stripe, "can't be blank")
20-
assert_error_message(changeset, :type, "can't be blank")
24+
25+
assert_validation_triggered(changeset, :endpoint, :required)
26+
assert_validation_triggered(changeset, :id_from_stripe, :required)
27+
assert_validation_triggered(changeset, :object_id, :required)
28+
assert_validation_triggered(changeset, :object_type, :required)
29+
assert_validation_triggered(changeset, :type, :required)
2130
end
2231

2332
test "sets :status to 'processing'" do

test/support/factories.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ defmodule CodeCorps.Factories do
156156
%CodeCorps.StripeEvent{
157157
endpoint: sequence(:endpoint, fn(_) -> Enum.random(~w{ connect platform }) end),
158158
id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"),
159+
object_id: "cus_123",
160+
object_type: "customer",
159161
status: sequence(:status, fn(_) -> Enum.random(~w{ unprocessed processed errored }) end),
160162
type: "test.type"
161163
}

web/models/stripe_event.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ defmodule CodeCorps.StripeEvent do
2424
schema "stripe_events" do
2525
field :endpoint, :string, null: false
2626
field :id_from_stripe, :string, null: false
27+
field :object_id, :string
28+
field :object_type, :string
2729
field :status, :string, default: "unprocessed"
2830
field :type, :string, null: false
2931
field :user_id, :string
@@ -37,8 +39,8 @@ defmodule CodeCorps.StripeEvent do
3739
"""
3840
def create_changeset(struct, params \\ %{}) do
3941
struct
40-
|> cast(params, [:endpoint, :id_from_stripe, :type, :user_id])
41-
|> validate_required([:endpoint, :id_from_stripe, :type])
42+
|> cast(params, [:endpoint, :id_from_stripe, :object_id, :object_type, :type, :user_id])
43+
|> validate_required([:endpoint, :id_from_stripe, :object_id, :object_type, :type])
4244
|> put_change(:status, "processing")
4345
|> validate_inclusion(:status, states)
4446
|> validate_inclusion(:endpoint, endpoints)

0 commit comments

Comments
 (0)