Wrap paddle actions with DB transactions (#2558)

* Wrap paddle actions with DB transactions

* Bow to credo

* Make the paddle transactions crash when applicable

* s/changeset/details
This commit is contained in:
Adam Rutkowski 2023-01-03 16:14:50 +01:00 committed by GitHub
parent 1785653b1e
commit 44afbaa2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 90 deletions

View File

@ -12,93 +12,28 @@ defmodule Plausible.Billing do
user_id |> active_subscription_query() |> Repo.exists?()
end
defp active_subscription_query(user_id) do
from s in Subscription,
where: s.user_id == ^user_id and s.status == "active",
order_by: [desc: s.inserted_at],
limit: 1
end
def subscription_created(params) do
params =
if present?(params["passthrough"]) do
params
else
user = Repo.get_by(Plausible.Auth.User, email: params["email"])
Map.put(params, "passthrough", user && user.id)
end
changeset = Subscription.changeset(%Subscription{}, format_subscription(params))
Repo.insert(changeset) |> after_subscription_update
Repo.transaction(fn ->
handle_subscription_created(params)
end)
end
def subscription_updated(params) do
subscription = Repo.get_by!(Subscription, paddle_subscription_id: params["subscription_id"])
changeset = Subscription.changeset(subscription, format_subscription(params))
Repo.update(changeset) |> after_subscription_update
Repo.transaction(fn ->
handle_subscription_updated(params)
end)
end
defp after_subscription_update({:ok, subscription}) do
user =
Repo.get(Plausible.Auth.User, subscription.user_id)
|> Map.put(:subscription, subscription)
user
|> maybe_remove_grace_period
|> check_lock_status
|> maybe_adjust_api_key_limits
end
defp after_subscription_update(err), do: err
def subscription_cancelled(params) do
subscription =
Repo.get_by(Subscription, paddle_subscription_id: params["subscription_id"])
|> Repo.preload(:user)
if subscription do
changeset =
Subscription.changeset(subscription, %{
status: params["status"]
})
case Repo.update(changeset) do
{:ok, updated} ->
PlausibleWeb.Email.cancellation_email(subscription.user)
|> Plausible.Mailer.send()
{:ok, updated}
err ->
err
end
else
{:ok, nil}
end
Repo.transaction(fn ->
handle_subscription_cancelled(params)
end)
end
def subscription_payment_succeeded(params) do
subscription = Repo.get_by(Subscription, paddle_subscription_id: params["subscription_id"])
if subscription do
{:ok, api_subscription} = paddle_api().get_subscription(subscription.paddle_subscription_id)
amount =
:erlang.float_to_binary(api_subscription["next_payment"]["amount"] / 1, decimals: 2)
changeset =
Subscription.changeset(subscription, %{
next_bill_amount: amount,
next_bill_date: api_subscription["next_payment"]["date"],
last_bill_date: api_subscription["last_payment"]["date"]
})
Repo.update(changeset)
else
{:ok, nil}
end
Repo.transaction(fn ->
handle_subscription_payment_succeeded(params)
end)
end
def change_plan(user, new_plan_id) do
@ -247,6 +182,71 @@ defmodule Plausible.Billing do
end
end
defp handle_subscription_created(params) do
params =
if present?(params["passthrough"]) do
params
else
user = Repo.get_by(Plausible.Auth.User, email: params["email"])
Map.put(params, "passthrough", user && user.id)
end
%Subscription{}
|> Subscription.changeset(format_subscription(params))
|> Repo.insert!()
|> after_subscription_update()
end
defp handle_subscription_updated(params) do
Subscription
|> Repo.get_by!(paddle_subscription_id: params["subscription_id"])
|> Subscription.changeset(format_subscription(params))
|> Repo.update!()
|> after_subscription_update()
end
defp handle_subscription_cancelled(params) do
subscription =
Subscription
|> Repo.get_by(paddle_subscription_id: params["subscription_id"])
|> Repo.preload(:user)
if subscription do
changeset =
Subscription.changeset(subscription, %{
status: params["status"]
})
updated = Repo.update!(changeset)
subscription
|> Map.fetch!(:user)
|> PlausibleWeb.Email.cancellation_email()
|> Plausible.Mailer.send()
updated
end
end
defp handle_subscription_payment_succeeded(params) do
subscription = Repo.get_by(Subscription, paddle_subscription_id: params["subscription_id"])
if subscription do
{:ok, api_subscription} = paddle_api().get_subscription(subscription.paddle_subscription_id)
amount =
:erlang.float_to_binary(api_subscription["next_payment"]["amount"] / 1, decimals: 2)
subscription
|> Subscription.changeset(%{
next_bill_amount: amount,
next_bill_date: api_subscription["next_payment"]["date"],
last_bill_date: api_subscription["last_payment"]["date"]
})
|> Repo.update!()
end
end
defp has_active_enterprise_subscription(user) do
Plausible.Repo.exists?(
from(s in Plausible.Billing.Subscription,
@ -287,24 +287,22 @@ defmodule Plausible.Billing do
if new_allowance > allowance_required do
user
|> Plausible.Auth.GracePeriod.remove_changeset()
|> Repo.update()
|> Repo.update!()
else
{:ok, user}
user
end
_ ->
{:ok, user}
user
end
end
defp check_lock_status({:ok, user}) do
defp check_lock_status(user) do
Plausible.Billing.SiteLocker.check_sites_for(user)
{:ok, user}
user
end
defp check_lock_status(err), do: err
defp maybe_adjust_api_key_limits({:ok, user}) do
defp maybe_adjust_api_key_limits(user) do
plan =
Repo.get_by(Plausible.Billing.EnterprisePlan,
user_id: user.id,
@ -317,10 +315,27 @@ defmodule Plausible.Billing do
Repo.update_all(api_keys, set: [hourly_request_limit: plan.hourly_api_request_limit])
end
{:ok, user}
user
end
defp maybe_adjust_api_key_limits(err), do: err
def paddle_api(), do: Application.fetch_env!(:plausible, :paddle_api)
defp active_subscription_query(user_id) do
from s in Subscription,
where: s.user_id == ^user_id and s.status == "active",
order_by: [desc: s.inserted_at],
limit: 1
end
defp after_subscription_update(subscription) do
user =
Plausible.Auth.User
|> Repo.get!(subscription.user_id)
|> Map.put(:subscription, subscription)
user
|> maybe_remove_grace_period()
|> check_lock_status()
|> maybe_adjust_api_key_limits()
end
end

View File

@ -68,8 +68,8 @@ defmodule PlausibleWeb.Api.PaddleController do
json(conn, "")
end
defp webhook_response({:error, changeset}, conn, _params) do
Logger.error("Error processing Paddle webhook: #{inspect(changeset)}")
defp webhook_response({:error, details}, conn, _params) do
Logger.error("Error processing Paddle webhook: #{inspect(details)}")
conn |> send_resp(400, "") |> halt
end