Skip to content

Commit 27fac66

Browse files
authored
Refactor Paddle webhook (#4938)
* Refactor Paddle webhook handler * Make Paddle webhook work with both old and new format and add EE prefix * Don't send EE enabled prefix from CE
1 parent c29b93f commit 27fac66

File tree

10 files changed

+195
-82
lines changed

10 files changed

+195
-82
lines changed

lib/mix/tasks/create_free_subscription.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ defmodule Mix.Tasks.CreateFreeSubscription do
1717
user = Repo.get(Plausible.Auth.User, user_id)
1818
{:ok, team} = Plausible.Teams.get_or_create(user)
1919

20-
Subscription.free(%{team_id: team.id})
20+
team
21+
|> Subscription.free()
2122
|> Repo.insert!()
2223

2324
IO.puts("Created a free subscription for user: #{user.name}")

lib/plausible/billing/billing.ex

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ defmodule Plausible.Billing do
22
use Plausible
33
use Plausible.Repo
44
require Plausible.Billing.Subscription.Status
5+
alias Plausible.Auth
56
alias Plausible.Billing.Subscription
6-
alias Plausible.Auth.User
77
alias Plausible.Teams
88

99
def subscription_created(params) do
@@ -44,25 +44,15 @@ defmodule Plausible.Billing do
4444
end
4545

4646
defp handle_subscription_created(params) do
47-
params =
48-
if present?(params["passthrough"]) do
49-
format_params(params)
50-
else
51-
user = Repo.get_by!(User, email: params["email"])
52-
{:ok, team} = Plausible.Teams.get_or_create(user)
53-
54-
params
55-
|> Map.put("passthrough", user.id)
56-
|> Map.put("team_id", team.id)
57-
end
47+
team = get_team!(params)
5848

5949
subscription_params =
6050
params
6151
|> format_subscription()
6252
|> add_last_bill_date(params)
6353

64-
%Subscription{}
65-
|> Subscription.changeset(subscription_params)
54+
team
55+
|> Subscription.create_changeset(subscription_params)
6656
|> Repo.insert!()
6757
|> after_subscription_update()
6858
end
@@ -86,10 +76,7 @@ defmodule Plausible.Billing do
8676
irrelevant? = params["old_status"] == "paused" && params["status"] == "past_due"
8777

8878
if subscription && not irrelevant? do
89-
params =
90-
params
91-
|> format_params()
92-
|> format_subscription()
79+
params = format_subscription(params)
9380

9481
subscription
9582
|> Subscription.changeset(params)
@@ -145,42 +132,66 @@ defmodule Plausible.Billing do
145132
end
146133
end
147134

148-
defp format_params(%{"passthrough" => passthrough} = params) do
149-
case String.split(to_string(passthrough), ";") do
150-
[user_id] ->
151-
user = Repo.get!(User, user_id)
152-
{:ok, team} = Plausible.Teams.get_or_create(user)
153-
Map.put(params, "team_id", team.id)
135+
defp get_team!(%{"passthrough" => passthrough}) do
136+
case parse_passthrough!(passthrough) do
137+
{:team_id, team_id} ->
138+
Teams.get!(team_id)
154139

155-
["user:" <> user_id, "team:" <> team_id] ->
156-
params
157-
|> Map.put("passthrough", user_id)
158-
|> Map.put("team_id", team_id)
140+
{:user_id, user_id} ->
141+
user = Repo.get!(Auth.User, user_id)
142+
{:ok, team} = Teams.get_or_create(user)
143+
team
159144
end
160145
end
161146

162-
defp format_params(params) do
163-
params
147+
defp get_team!(_params) do
148+
raise "Missing passthrough"
149+
end
150+
151+
defp parse_passthrough!(passthrough) do
152+
{user_id, team_id} =
153+
case String.split(to_string(passthrough), ";") do
154+
["ee:true", "user:" <> user_id, "team:" <> team_id] ->
155+
{user_id, team_id}
156+
157+
["ee:true", "user:" <> user_id] ->
158+
{user_id, "0"}
159+
160+
# NOTE: legacy pattern, to be removed in a follow-up
161+
["user:" <> user_id, "team:" <> team_id] ->
162+
{user_id, team_id}
163+
164+
# NOTE: legacy pattern, to be removed in a follow-up
165+
[user_id] ->
166+
{user_id, "0"}
167+
168+
_ ->
169+
raise "Invalid passthrough sent via Paddle: #{inspect(passthrough)}"
170+
end
171+
172+
case {Integer.parse(user_id), Integer.parse(team_id)} do
173+
{{user_id, ""}, {0, ""}} when user_id > 0 ->
174+
{:user_id, user_id}
175+
176+
{{_user_id, ""}, {team_id, ""}} when team_id > 0 ->
177+
{:team_id, team_id}
178+
179+
_ ->
180+
raise "Invalid passthrough sent via Paddle: #{inspect(passthrough)}"
181+
end
164182
end
165183

166184
defp format_subscription(params) do
167-
subscription_params = %{
185+
%{
168186
paddle_subscription_id: params["subscription_id"],
169187
paddle_plan_id: params["subscription_plan_id"],
170188
cancel_url: params["cancel_url"],
171189
update_url: params["update_url"],
172-
user_id: params["passthrough"],
173190
status: params["status"],
174191
next_bill_date: params["next_bill_date"],
175192
next_bill_amount: params["unit_price"] || params["new_unit_price"],
176193
currency_code: params["currency"]
177194
}
178-
179-
if team_id = params["team_id"] do
180-
Map.put(subscription_params, :team_id, team_id)
181-
else
182-
subscription_params
183-
end
184195
end
185196

186197
defp add_last_bill_date(subscription_params, paddle_params) do
@@ -193,10 +204,6 @@ defmodule Plausible.Billing do
193204
end
194205
end
195206

196-
defp present?(""), do: false
197-
defp present?(nil), do: false
198-
defp present?(_), do: true
199-
200207
@spec format_price(Money.t()) :: String.t()
201208
def format_price(money) do
202209
Money.to_string!(money, fractional_digits: 2, no_fraction_if_integer: true)

lib/plausible/billing/subscription.ex

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule Plausible.Billing.Subscription do
1919
:currency_code
2020
]
2121

22-
@optional_fields [:last_bill_date, :team_id]
22+
@optional_fields [:last_bill_date]
2323

2424
schema "subscriptions" do
2525
field :paddle_subscription_id, :string
@@ -37,22 +37,28 @@ defmodule Plausible.Billing.Subscription do
3737
timestamps()
3838
end
3939

40-
def changeset(model, attrs \\ %{}) do
41-
model
40+
def create_changeset(team, attrs \\ %{}) do
41+
%__MODULE__{}
42+
|> changeset(attrs)
43+
|> put_assoc(:team, team)
44+
end
45+
46+
def changeset(subscription, attrs \\ %{}) do
47+
subscription
4248
|> cast(attrs, @required_fields ++ @optional_fields)
4349
|> validate_required(@required_fields)
4450
|> unique_constraint(:paddle_subscription_id)
4551
end
4652

47-
def free(attrs \\ %{}) do
53+
def free(team, attrs \\ %{}) do
4854
%__MODULE__{
4955
paddle_plan_id: "free_10k",
5056
status: Subscription.Status.active(),
5157
next_bill_amount: "0",
5258
currency_code: "EUR"
5359
}
5460
|> cast(attrs, @required_fields ++ @optional_fields)
55-
|> validate_required([:team_id])
61+
|> put_assoc(:team, team)
5662
|> unique_constraint(:paddle_subscription_id)
5763
end
5864
end

lib/plausible/teams.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ defmodule Plausible.Teams do
1212

1313
@accept_traffic_until_free ~D[2135-01-01]
1414

15+
@spec get!(pos_integer()) :: Teams.Team.t()
16+
def get!(team_id) do
17+
Repo.get!(Teams.Team, team_id)
18+
end
19+
1520
@spec get_owner(Teams.Team.t()) ::
1621
{:ok, Plausible.Auth.User.t()} | {:error, :no_owner | :multiple_owners}
1722
def get_owner(team) do

lib/plausible_web/components/billing/billing.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule PlausibleWeb.Components.Billing do
22
@moduledoc false
33

44
use PlausibleWeb, :component
5+
use Plausible
56

67
require Plausible.Billing.Subscription.Status
78
alias Plausible.Billing.{Subscription, Subscriptions}
@@ -237,9 +238,9 @@ defmodule PlausibleWeb.Components.Billing do
237238

238239
passthrough =
239240
if assigns.team do
240-
"user:#{assigns.user.id};team:#{assigns.team.id}"
241+
"ee:#{ee?()};user:#{assigns.user.id};team:#{assigns.team.id}"
241242
else
242-
assigns.user.id
243+
"ee:#{ee?()};user:#{assigns.user.id}"
243244
end
244245

245246
assigns =

0 commit comments

Comments
 (0)