analytics/lib/plausible_web/controllers/billing_controller.ex
hq1 f755b20569
Lock traffic notifications (#3641)
* Update communication

* Remove an unreachable function (mistyped)

* [migration] Make accept_traffic_until a date

* Fix typo

* Set `accept_traffic_until` when creating a site

* Update sites `accept_traffic_until` on subscription change

* Add a note to yearly cancellation notification

* Rephrase annual e-mail for clarity

* Pass the small build test

* Add email notifications

* Fixup

* Implement `accept_traffic_until` notification worker

* Fixup - no need to test this for small build

* Update moduledoc

* Move moduletag

* s/sent_at/sent_on

* Use WHERE NOT EXISTS instead of LEFT JOIN

* Use upsert when tracking notifications sent

* Store sent marker before actually sending notification

* Prefer to keep `accept_traffic_until` on the user record

This gives us a single source of truth, addresses cases like
ownership transparently, simplifies the code and enables CRM toggles.
The only downside is that there's another join performed in the
Sites.Cache full refresh - in this case, small refreshes are
skipped - but this is fine, since the traffic will be let in
anyway.

* Expose `accepted_traffic_until` in the CRM

* Update lib/plausible/auth/user.ex

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>

* Preload owner in CRM

* Use the offset parameter in trial over e-mail contents

* Format

* Harden cache test

---------

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>
2023-12-28 08:42:27 +01:00

154 lines
4.9 KiB
Elixir

defmodule PlausibleWeb.BillingController do
use PlausibleWeb, :controller
use Plausible.Repo
require Logger
require Plausible.Billing.Subscription.Status
alias Plausible.Billing
alias Plausible.Billing.{Plans, Subscription}
plug PlausibleWeb.RequireAccountPlug
def ping_subscription(%Plug.Conn{} = conn, _params) do
subscribed? = Billing.has_active_subscription?(conn.assigns.current_user.id)
json(conn, %{is_subscribed: subscribed?})
end
def choose_plan(conn, _params) do
user = conn.assigns.current_user
if Plausible.Auth.enterprise_configured?(user) do
redirect(conn, to: Routes.billing_path(conn, :upgrade_to_enterprise_plan))
else
render(conn, "choose_plan.html",
skip_plausible_tracking: true,
user: user,
layout: {PlausibleWeb.LayoutView, "focus.html"},
connect_live_socket: true
)
end
end
def upgrade_to_enterprise_plan(conn, _params) do
user = Plausible.Users.with_subscription(conn.assigns.current_user)
{latest_enterprise_plan, price} = Plans.latest_enterprise_plan_with_price(user)
subscription_resumable? = Plausible.Billing.Subscriptions.resumable?(user.subscription)
subscribed_to_latest? =
subscription_resumable? &&
user.subscription.paddle_plan_id == latest_enterprise_plan.paddle_plan_id
cond do
Subscription.Status.in?(user.subscription, [
Subscription.Status.past_due(),
Subscription.Status.paused()
]) ->
redirect(conn, to: Routes.auth_path(conn, :user_settings))
subscribed_to_latest? ->
render(conn, "change_enterprise_plan_contact_us.html",
skip_plausible_tracking: true,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
true ->
render(conn, "upgrade_to_enterprise_plan.html",
user: user,
latest_enterprise_plan: latest_enterprise_plan,
price: price,
subscription_resumable: subscription_resumable?,
contact_link: "https://plausible.io/contact",
skip_plausible_tracking: true,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
end
def upgrade_success(conn, _params) do
render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
end
def change_plan_preview(conn, %{"plan_id" => new_plan_id}) do
user = conn.assigns.current_user
case preview_subscription(user, new_plan_id) do
{:ok, {subscription, preview_info}} ->
render(conn, "change_plan_preview.html",
back_link: Routes.billing_path(conn, :choose_plan),
skip_plausible_tracking: true,
subscription: subscription,
preview_info: preview_info,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
_ ->
msg =
"Something went wrong with loading your plan change information. Please try again, or contact us at support@plausible.io if the issue persists."
Sentry.capture_message("Error loading change plan preview",
extra: %{
message: msg,
new_plan_id: new_plan_id,
user_id: user.id
}
)
conn
|> put_flash(:error, msg)
|> redirect(to: Routes.billing_path(conn, :choose_plan))
end
end
def change_plan(conn, %{"new_plan_id" => new_plan_id}) do
case Billing.change_plan(conn.assigns.current_user, new_plan_id) do
{:ok, _subscription} ->
conn
|> put_flash(:success, "Plan changed successfully")
|> redirect(to: "/settings")
{:error, e} ->
msg =
case e do
{:over_plan_limits, exceeded_limits} ->
"Unable to subscribe to this plan because the following limits are exceeded: #{PlausibleWeb.TextHelpers.pretty_list(exceeded_limits)}"
%{"code" => 147} ->
# https://developer.paddle.com/api-reference/intro/api-error-codes
"We were unable to charge your card. Click 'update billing info' to update your payment details and try again."
%{"message" => msg} when not is_nil(msg) ->
msg
_ ->
"Something went wrong. Please try again or contact support at support@plausible.io"
end
Sentry.capture_message("Error changing plans",
extra: %{
errors: inspect(e),
message: msg,
new_plan_id: new_plan_id,
user_id: conn.assigns[:current_user].id
}
)
conn
|> put_flash(:error, msg)
|> redirect(to: "/settings")
end
end
defp preview_subscription(%{id: user_id}, new_plan_id) do
subscription = Billing.active_subscription_for(user_id)
if subscription do
with {:ok, preview_info} <- Billing.change_plan_preview(subscription, new_plan_id) do
{:ok, {subscription, preview_info}}
end
else
{:error, :no_subscription}
end
end
end