| defmodule PlausibleWeb.Api.PaddleController do |
| use PlausibleWeb, :controller |
| use Plausible.Repo |
| require Logger |
|
|
| plug :verify_signature when action in [:webhook] |
|
|
| def webhook(conn, %{"alert_name" => "subscription_created"} = params) do |
| Plausible.Billing.subscription_created(params) |
| |> webhook_response(conn, params) |
| end |
|
|
| def webhook(conn, %{"alert_name" => "subscription_updated"} = params) do |
| Plausible.Billing.subscription_updated(params) |
| |> webhook_response(conn, params) |
| end |
|
|
| def webhook(conn, %{"alert_name" => "subscription_cancelled"} = params) do |
| Plausible.Billing.subscription_cancelled(params) |
| |> webhook_response(conn, params) |
| end |
|
|
| def webhook(conn, %{"alert_name" => "subscription_payment_succeeded"} = params) do |
| Plausible.Billing.subscription_payment_succeeded(params) |
| |> webhook_response(conn, params) |
| end |
|
|
| def webhook(conn, _params) do |
| send_resp(conn, 404, "") |> halt |
| end |
|
|
| @default_currency_fallback :EUR |
|
|
| def currency(conn, _params) do |
| plan_id = get_currency_reference_plan_id() |
| customer_ip = PlausibleWeb.RemoteIP.get(conn) |
|
|
| result = |
| Plausible.Cache.Adapter.fetch(:customer_currency, {plan_id, customer_ip}, fn -> |
| case Plausible.Billing.PaddleApi.fetch_prices([plan_id], customer_ip) do |
| {:ok, %{^plan_id => money}} -> |
| {:ok, money.currency} |
|
|
| error -> |
| Sentry.capture_message("Failed to fetch currency reference plan", |
| extra: %{error: inspect(error)} |
| ) |
|
|
| {:error, :fetch_prices_failed} |
| end |
| end) |
|
|
| case result do |
| {:ok, currency} -> |
| conn |
| |> put_status(200) |
| |> json(%{currency: Cldr.Currency.currency_for_code!(currency).narrow_symbol}) |
|
|
| {:error, :fetch_prices_failed} -> |
| conn |
| |> put_status(200) |
| |> json(%{ |
| currency: Cldr.Currency.currency_for_code!(@default_currency_fallback).narrow_symbol |
| }) |
| end |
| end |
|
|
| def verify_signature(conn, _opts) do |
| signature = Base.decode64!(conn.params["p_signature"]) |
|
|
| msg = |
| Map.delete(conn.params, "p_signature") |
| |> Enum.map(fn {key, val} -> {key, "#{val}"} end) |
| |> List.keysort(0) |
| |> PhpSerializer.serialize() |
|
|
| [key_entry] = :public_key.pem_decode(get_paddle_key()) |
|
|
| public_key = :public_key.pem_entry_decode(key_entry) |
|
|
| if :public_key.verify(msg, :sha, signature, public_key) do |
| conn |
| else |
| send_resp(conn, 400, "") |> halt |
| end |
| end |
|
|
| @paddle_currency_reference_plan_id "857097" |
| @paddle_sandbox_currency_reference_plan_id "63842" |
| defp get_currency_reference_plan_id() do |
| if Application.get_env(:plausible, :environment) == "staging" do |
| @paddle_sandbox_currency_reference_plan_id |
| else |
| @paddle_currency_reference_plan_id |
| end |
| end |
|
|
| @paddle_prod_key File.read!("priv/paddle.pem") |
| @paddle_sandbox_key File.read!("priv/paddle_sandbox.pem") |
|
|
| defp get_paddle_key() do |
| if Application.get_env(:plausible, :environment) == "staging" do |
| @paddle_sandbox_key |
| else |
| @paddle_prod_key |
| end |
| end |
|
|
| defp webhook_response({:ok, _}, conn, _params) do |
| json(conn, "") |
| end |
|
|
| defp webhook_response({:error, details}, conn, _params) do |
| Logger.error("Error processing Paddle webhook: #{inspect(details)}") |
|
|
| conn |> send_resp(400, "") |> halt |
| end |
| end |
|
|