mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 17:44:43 +03:00
Restrict subscribing to a plan when exceeding its limits + warning for losing feature access (#3461)
* fix the styling of the red text notice under checkout link * avoid some code repetition * simplify rendering the change_plan_link * refactor disabling checkout link and showing disabled message * disable change plan and upgrade link when exceeding pageview limit * disable checkout when exceeding team member limit * disable checkout when site limit exceeded * extract checkout related code in a separate function * stick to a single order of features * losing features warning * fix back link from change-plan-preview * create Quota.exceeded_limits function * restrict subscribing with exceeded limits on the API level too * use with instead of case Co-authored-by: Vini Brasil <vini@hey.com> * use :map type instead of :any for user Co-authored-by: Vini Brasil <vini@hey.com> * create Quota.usage function --------- Co-authored-by: Vini Brasil <vini@hey.com>
This commit is contained in:
parent
0c8b3d7992
commit
8cc7bce689
@ -1,7 +1,7 @@
|
||||
defmodule Plausible.Billing do
|
||||
use Plausible.Repo
|
||||
require Plausible.Billing.Subscription.Status
|
||||
alias Plausible.Billing.Subscription
|
||||
alias Plausible.Billing.{Subscription, Plans, Quota}
|
||||
|
||||
@spec active_subscription_for(integer()) :: Subscription.t() | nil
|
||||
def active_subscription_for(user_id) do
|
||||
@ -39,7 +39,13 @@ defmodule Plausible.Billing do
|
||||
|
||||
def change_plan(user, new_plan_id) do
|
||||
subscription = active_subscription_for(user.id)
|
||||
plan = Plans.find(new_plan_id)
|
||||
|
||||
with :ok <- Quota.ensure_can_subscribe_to_plan(user, plan),
|
||||
do: do_change_plan(subscription, new_plan_id)
|
||||
end
|
||||
|
||||
defp do_change_plan(subscription, new_plan_id) do
|
||||
res =
|
||||
paddle_api().update_subscription(subscription.paddle_subscription_id, %{
|
||||
plan_id: new_plan_id
|
||||
|
@ -79,9 +79,9 @@ defmodule Plausible.Billing.Plans do
|
||||
do: yearly_product_id
|
||||
end
|
||||
|
||||
defp find(nil), do: nil
|
||||
def find(nil), do: nil
|
||||
|
||||
defp find(product_id) do
|
||||
def find(product_id) do
|
||||
Enum.find(all(), fn plan ->
|
||||
product_id in [plan.monthly_product_id, plan.yearly_product_id]
|
||||
end)
|
||||
|
@ -8,6 +8,21 @@ defmodule Plausible.Billing.Quota do
|
||||
alias Plausible.Billing.{Plan, Plans, Subscription, EnterprisePlan, Feature}
|
||||
alias Plausible.Billing.Feature.{Goals, RevenueGoals, Funnels, Props}
|
||||
|
||||
def usage(user, opts \\ []) do
|
||||
basic_usage = %{
|
||||
monthly_pageviews: monthly_pageview_usage(user),
|
||||
team_members: team_member_usage(user),
|
||||
sites: site_usage(user)
|
||||
}
|
||||
|
||||
if Keyword.get(opts, :with_features) == true do
|
||||
basic_usage
|
||||
|> Map.put(:features, features_usage(user))
|
||||
else
|
||||
basic_usage
|
||||
end
|
||||
end
|
||||
|
||||
@limit_sites_since ~D[2021-05-05]
|
||||
@spec site_limit(Plausible.Auth.User.t()) :: non_neg_integer() | :unlimited
|
||||
@doc """
|
||||
@ -169,10 +184,30 @@ defmodule Plausible.Billing.Quota do
|
||||
]
|
||||
|
||||
Enum.reduce(queries, [], fn {feature, query}, acc ->
|
||||
if Plausible.Repo.exists?(query), do: [feature | acc], else: acc
|
||||
if Plausible.Repo.exists?(query), do: acc ++ [feature], else: acc
|
||||
end)
|
||||
end
|
||||
|
||||
def ensure_can_subscribe_to_plan(user, %Plan{} = plan) do
|
||||
case exceeded_limits(usage(user), plan) do
|
||||
[] -> :ok
|
||||
exceeded_limits -> {:error, %{exceeded_limits: exceeded_limits}}
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_can_subscribe_to_plan(_user, nil), do: :ok
|
||||
|
||||
def exceeded_limits(usage, %Plan{} = plan) do
|
||||
for {usage_field, limit_field} <- [
|
||||
{:monthly_pageviews, :monthly_pageview_limit},
|
||||
{:team_members, :team_member_limit},
|
||||
{:sites, :site_limit}
|
||||
],
|
||||
!within_limit?(Map.get(usage, usage_field), Map.get(plan, limit_field)) do
|
||||
limit_field
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of features the user can use. Trial users have the
|
||||
ability to use all features during their trial.
|
||||
|
@ -279,12 +279,28 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
|> String.replace(".00", "")
|
||||
end
|
||||
|
||||
attr :id, :string, required: true
|
||||
attr :paddle_product_id, :string, required: true
|
||||
attr :checkout_disabled, :boolean, default: false
|
||||
attr :user, :map, required: true
|
||||
attr :confirm_message, :any, default: nil
|
||||
slot :inner_block, required: true
|
||||
|
||||
def paddle_button(assigns) do
|
||||
confirmed =
|
||||
if assigns.confirm_message, do: "confirm(\"#{assigns.confirm_message}\")", else: "true"
|
||||
|
||||
assigns = assign(assigns, :confirmed, confirmed)
|
||||
|
||||
~H"""
|
||||
<button
|
||||
id={@id}
|
||||
onclick={"Paddle.Checkout.open(#{Jason.encode!(%{product: @paddle_product_id, email: @user.email, disableLogout: true, passthrough: @user.id, success: Routes.billing_path(PlausibleWeb.Endpoint, :upgrade_success), theme: "none"})})"}
|
||||
class="w-full mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 text-white bg-indigo-600 hover:bg-indigo-500"
|
||||
onclick={"if (#{@confirmed}) {Paddle.Checkout.open(#{Jason.encode!(%{product: @paddle_product_id, email: @user.email, disableLogout: true, passthrough: @user.id, success: Routes.billing_path(PlausibleWeb.Endpoint, :upgrade_success), theme: "none"})})}"}
|
||||
class={[
|
||||
"w-full mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 text-white",
|
||||
!@checkout_disabled && "bg-indigo-600 hover:bg-indigo-500",
|
||||
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
|
||||
]}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</button>
|
||||
|
@ -126,7 +126,15 @@ defmodule PlausibleWeb.BillingController do
|
||||
def change_plan_preview(conn, %{"plan_id" => new_plan_id}) do
|
||||
with {:ok, {subscription, preview_info}} <-
|
||||
preview_subscription(conn.assigns.current_user, new_plan_id) do
|
||||
back_action =
|
||||
if FunWithFlags.enabled?(:business_tier, for: conn.assigns.current_user) do
|
||||
:choose_plan
|
||||
else
|
||||
:change_plan_form
|
||||
end
|
||||
|
||||
render(conn, "change_plan_preview.html",
|
||||
back_link: Routes.billing_path(conn, back_action),
|
||||
skip_plausible_tracking: true,
|
||||
subscription: subscription,
|
||||
preview_info: preview_info,
|
||||
@ -139,17 +147,20 @@ defmodule PlausibleWeb.BillingController do
|
||||
end
|
||||
|
||||
def change_plan(conn, %{"new_plan_id" => new_plan_id}) do
|
||||
case Billing.change_plan(conn.assigns[:current_user], 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} ->
|
||||
# https://developer.paddle.com/api-reference/intro/api-error-codes
|
||||
msg =
|
||||
case e do
|
||||
%{exceeded_limits: exceeded_limits} ->
|
||||
"Unable to subscribe to this plan because the following limits are exceeded: #{inspect(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) ->
|
||||
|
@ -23,7 +23,7 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
Users.with_subscription(user_id)
|
||||
end)
|
||||
|> assign_new(:usage, fn %{user: user} ->
|
||||
Quota.monthly_pageview_usage(user)
|
||||
Quota.usage(user, with_features: true)
|
||||
end)
|
||||
|> assign_new(:owned_plan, fn %{user: %{subscription: subscription}} ->
|
||||
Plans.get_regular_plan(subscription, only_non_expired: true)
|
||||
@ -42,7 +42,7 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
usage: usage,
|
||||
available_volumes: available_volumes
|
||||
} ->
|
||||
default_selected_volume(owned_plan, usage, available_volumes)
|
||||
default_selected_volume(owned_plan, usage.monthly_pageviews, available_volumes)
|
||||
end)
|
||||
|> assign_new(:selected_interval, fn %{current_interval: current_interval} ->
|
||||
current_interval || :monthly
|
||||
@ -118,7 +118,8 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
<.enterprise_plan_box benefits={@enterprise_benefits} />
|
||||
</div>
|
||||
<p class="mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400">
|
||||
<.usage usage={@usage} />
|
||||
You have used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage.monthly_pageviews) %></b>
|
||||
billable pageviews in the last 30 days
|
||||
</p>
|
||||
<.pageview_limit_notice :if={!@owned_plan} />
|
||||
<.help_links />
|
||||
@ -160,8 +161,8 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
|
||||
defp default_selected_volume(%Plan{monthly_pageview_limit: limit}, _, _), do: limit
|
||||
|
||||
defp default_selected_volume(_, usage, available_volumes) do
|
||||
Enum.find(available_volumes, &(usage < &1)) || :enterprise
|
||||
defp default_selected_volume(_, pageview_usage, available_volumes) do
|
||||
Enum.find(available_volumes, &(pageview_usage < &1)) || :enterprise
|
||||
end
|
||||
|
||||
defp current_user_subscription_interval(subscription) do
|
||||
@ -261,30 +262,10 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
</div>
|
||||
<div>
|
||||
<.render_price_info available={@available} {assigns} />
|
||||
<%= cond do %>
|
||||
<% !@available -> %>
|
||||
<.contact_button class="bg-indigo-600 hover:bg-indigo-500 text-white" />
|
||||
<% @owned_plan && Plausible.Billing.Subscriptions.resumable?(@user.subscription) -> %>
|
||||
<.render_change_plan_link
|
||||
paddle_product_id={get_paddle_product_id(@plan_to_render, @selected_interval)}
|
||||
text={
|
||||
change_plan_link_text(
|
||||
@owned_plan,
|
||||
@plan_to_render,
|
||||
@current_interval,
|
||||
@selected_interval
|
||||
)
|
||||
}
|
||||
{assigns}
|
||||
/>
|
||||
<% true -> %>
|
||||
<.paddle_button
|
||||
id={"#{@kind}-checkout"}
|
||||
paddle_product_id={get_paddle_product_id(@plan_to_render, @selected_interval)}
|
||||
{assigns}
|
||||
>
|
||||
Upgrade
|
||||
</.paddle_button>
|
||||
<%= if @available do %>
|
||||
<.checkout id={"#{@kind}-checkout"} {assigns} />
|
||||
<% else %>
|
||||
<.contact_button class="bg-indigo-600 hover:bg-indigo-500 text-white" />
|
||||
<% end %>
|
||||
</div>
|
||||
<%= if @kind == :growth && @plan_to_render.generation < 4 do %>
|
||||
@ -301,6 +282,67 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
"""
|
||||
end
|
||||
|
||||
defp checkout(assigns) do
|
||||
paddle_product_id = get_paddle_product_id(assigns.plan_to_render, assigns.selected_interval)
|
||||
change_plan_link_text = change_plan_link_text(assigns)
|
||||
|
||||
exceeds_some_limit = Quota.exceeded_limits(assigns.usage, assigns.plan_to_render) != []
|
||||
|
||||
billing_details_expired =
|
||||
assigns.user.subscription &&
|
||||
assigns.user.subscription.status in [
|
||||
Subscription.Status.paused(),
|
||||
Subscription.Status.past_due()
|
||||
]
|
||||
|
||||
{checkout_disabled, disabled_message} =
|
||||
cond do
|
||||
change_plan_link_text == "Currently on this plan" ->
|
||||
{true, nil}
|
||||
|
||||
assigns.available && exceeds_some_limit ->
|
||||
{true, "Your usage exceeds this plan"}
|
||||
|
||||
billing_details_expired ->
|
||||
{true, "Please update your billing details first"}
|
||||
|
||||
true ->
|
||||
{false, nil}
|
||||
end
|
||||
|
||||
features_to_lose = assigns.usage.features -- assigns.plan_to_render.features
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:paddle_product_id, paddle_product_id)
|
||||
|> assign(:change_plan_link_text, change_plan_link_text)
|
||||
|> assign(:checkout_disabled, checkout_disabled)
|
||||
|> assign(:disabled_message, disabled_message)
|
||||
|> assign(:confirm_message, losing_features_message(features_to_lose))
|
||||
|
||||
~H"""
|
||||
<%= if @owned_plan && Plausible.Billing.Subscriptions.resumable?(@user.subscription) do %>
|
||||
<.change_plan_link {assigns} />
|
||||
<% else %>
|
||||
<.paddle_button {assigns}>Upgrade</.paddle_button>
|
||||
<% end %>
|
||||
<p :if={@disabled_message} class="h-0 text-center text-sm text-red-700 dark:text-red-500">
|
||||
<%= @disabled_message %>
|
||||
</p>
|
||||
"""
|
||||
end
|
||||
|
||||
defp losing_features_message([]), do: nil
|
||||
|
||||
defp losing_features_message(features_to_lose) do
|
||||
features_list_str =
|
||||
features_to_lose
|
||||
|> Enum.map(& &1.display_name)
|
||||
|> PlausibleWeb.TextHelpers.pretty_join()
|
||||
|
||||
"This plan does not support #{features_list_str}, which you are currently using. Please note that by subscribing to this plan you will lose access to #{if length(features_to_lose) == 1, do: "this feature", else: "these features"}."
|
||||
end
|
||||
|
||||
defp growth_grandfathering_notice(assigns) do
|
||||
~H"""
|
||||
<ul class="mt-8 space-y-3 text-sm leading-6 text-gray-600 text-justify dark:text-gray-100 xl:mt-10">
|
||||
@ -333,39 +375,20 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_change_plan_link(assigns) do
|
||||
~H"""
|
||||
<.change_plan_link
|
||||
plan_already_owned={@text == "Currently on this plan"}
|
||||
billing_details_expired={
|
||||
@user.subscription &&
|
||||
@user.subscription.status in [Subscription.Status.past_due(), Subscription.Status.paused()]
|
||||
}
|
||||
{assigns}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
||||
defp change_plan_link(assigns) do
|
||||
~H"""
|
||||
<.link
|
||||
id={"#{@kind}-checkout"}
|
||||
onclick={if @confirm_message, do: "if (!confirm(\"#{@confirm_message}\")) {e.preventDefault()}"}
|
||||
href={Routes.billing_path(PlausibleWeb.Endpoint, :change_plan_preview, @paddle_product_id)}
|
||||
class={[
|
||||
"w-full mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 text-white",
|
||||
!(@plan_already_owned || @billing_details_expired) && "bg-indigo-600 hover:bg-indigo-500",
|
||||
(@plan_already_owned || @billing_details_expired) &&
|
||||
"pointer-events-none bg-gray-400 dark:bg-gray-600"
|
||||
!@checkout_disabled && "bg-indigo-600 hover:bg-indigo-500",
|
||||
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
|
||||
]}
|
||||
>
|
||||
<%= @text %>
|
||||
<%= @change_plan_link_text %>
|
||||
</.link>
|
||||
<p
|
||||
:if={@billing_details_expired && !@plan_already_owned}
|
||||
class="text-center text-sm text-red-700 dark:text-red-500"
|
||||
>
|
||||
Please update your billing details first
|
||||
</p>
|
||||
"""
|
||||
end
|
||||
|
||||
@ -446,13 +469,6 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
"""
|
||||
end
|
||||
|
||||
defp usage(assigns) do
|
||||
~H"""
|
||||
You have used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b>
|
||||
billable pageviews in the last 30 days
|
||||
"""
|
||||
end
|
||||
|
||||
defp pageview_limit_notice(assigns) do
|
||||
~H"""
|
||||
<div class="mt-12 mx-auto mt-6 max-w-2xl">
|
||||
@ -585,10 +601,12 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
|
||||
defp change_plan_link_text(
|
||||
%Plan{kind: from_kind, monthly_pageview_limit: from_volume},
|
||||
%Plan{kind: to_kind, monthly_pageview_limit: to_volume},
|
||||
from_interval,
|
||||
to_interval
|
||||
%{
|
||||
owned_plan: %Plan{kind: from_kind, monthly_pageview_limit: from_volume},
|
||||
plan_to_render: %Plan{kind: to_kind, monthly_pageview_limit: to_volume},
|
||||
current_interval: from_interval,
|
||||
selected_interval: to_interval
|
||||
} = _assigns
|
||||
) do
|
||||
cond do
|
||||
from_kind == :business && to_kind == :growth ->
|
||||
@ -611,6 +629,8 @@ defmodule PlausibleWeb.Live.ChoosePlan do
|
||||
end
|
||||
end
|
||||
|
||||
defp change_plan_link_text(_), do: nil
|
||||
|
||||
defp get_available_volumes(%{business: business_plans, growth: growth_plans}) do
|
||||
growth_volumes = Enum.map(growth_plans, & &1.monthly_pageview_limit)
|
||||
business_volumes = Enum.map(business_plans, & &1.monthly_pageview_limit)
|
||||
|
@ -66,7 +66,7 @@
|
||||
|
||||
<div class="flex items-center justify-between mt-10">
|
||||
<span class="flex rounded-md shadow-sm">
|
||||
<a href="<%= Routes.billing_path(@conn, :change_plan_form) %>" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 dark:active:text-gray-200 active:bg-gray-50 transition ease-in-out duration-150">
|
||||
<a href="<%= @back_link %>" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 dark:active:text-gray-200 active:bg-gray-50 transition ease-in-out duration-150">
|
||||
Back
|
||||
</a>
|
||||
</span>
|
||||
|
33
lib/plausible_web/views/text_helpers.ex
Normal file
33
lib/plausible_web/views/text_helpers.ex
Normal file
@ -0,0 +1,33 @@
|
||||
defmodule PlausibleWeb.TextHelpers do
|
||||
@moduledoc false
|
||||
|
||||
@spec pretty_join([String.t()]) :: String.t()
|
||||
|
||||
@doc """
|
||||
Turns a list of strings into a string and replaces the last comma
|
||||
with the word "and".
|
||||
|
||||
### Examples:
|
||||
|
||||
iex> ["one"] |> PlausibleWeb.TextHelpers.pretty_join()
|
||||
"one"
|
||||
|
||||
iex> ["one", "two"] |> PlausibleWeb.TextHelpers.pretty_join()
|
||||
"one and two"
|
||||
|
||||
iex> ["one", "two", "three"] |> PlausibleWeb.TextHelpers.pretty_join()
|
||||
"one, two and three"
|
||||
"""
|
||||
def pretty_join([str]), do: str
|
||||
|
||||
def pretty_join(list) do
|
||||
[last_string | rest] = Enum.reverse(list)
|
||||
|
||||
rest_string =
|
||||
rest
|
||||
|> Enum.reverse()
|
||||
|> Enum.join(", ")
|
||||
|
||||
"#{rest_string} and #{last_string}"
|
||||
end
|
||||
end
|
@ -1,6 +1,6 @@
|
||||
defmodule Plausible.Billing.QuotaTest do
|
||||
use Plausible.DataCase, async: true
|
||||
alias Plausible.Billing.Quota
|
||||
alias Plausible.Billing.{Quota, Plans}
|
||||
alias Plausible.Billing.Feature.{Goals, RevenueGoals, Funnels, Props, StatsAPI}
|
||||
|
||||
@legacy_plan_id "558746"
|
||||
@ -115,6 +115,20 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "exceeded_limits/2" do
|
||||
test "returns limits that are exceeded" do
|
||||
usage = %{
|
||||
monthly_pageviews: 10_001,
|
||||
team_members: 2,
|
||||
sites: 51
|
||||
}
|
||||
|
||||
plan = Plans.find(@v3_plan_id)
|
||||
|
||||
assert Quota.exceeded_limits(usage, plan) == [:monthly_pageview_limit, :site_limit]
|
||||
end
|
||||
end
|
||||
|
||||
describe "monthly_pageview_limit/1" do
|
||||
test "is based on the plan if user is on a legacy plan" do
|
||||
user = insert(:user, subscription: build(:subscription, paddle_plan_id: @legacy_plan_id))
|
||||
@ -427,7 +441,7 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
steps = Enum.map(goals, &%{"goal_id" => &1.id})
|
||||
Plausible.Funnels.create(site, "dummy", steps)
|
||||
|
||||
assert [RevenueGoals, Funnels, Props] == Quota.features_usage(user)
|
||||
assert [Props, Funnels, RevenueGoals] == Quota.features_usage(user)
|
||||
end
|
||||
|
||||
test "accounts only for sites the user owns" do
|
||||
|
@ -4,6 +4,8 @@ defmodule PlausibleWeb.BillingControllerTest do
|
||||
require Plausible.Billing.Subscription.Status
|
||||
alias Plausible.Billing.Subscription
|
||||
|
||||
@v4_growth_plan "857097"
|
||||
|
||||
describe "GET /upgrade" do
|
||||
setup [:create_user, :log_in]
|
||||
|
||||
@ -75,6 +77,25 @@ defmodule PlausibleWeb.BillingControllerTest do
|
||||
describe "POST /change-plan" do
|
||||
setup [:create_user, :log_in]
|
||||
|
||||
test "errors if usage exceeds some limit on the new plan", %{conn: conn, user: user} do
|
||||
insert(:subscription, user: user, paddle_plan_id: "123123")
|
||||
|
||||
insert(:site,
|
||||
memberships: [
|
||||
build(:site_membership, user: user, role: :owner),
|
||||
build(:site_membership, user: build(:user)),
|
||||
build(:site_membership, user: build(:user)),
|
||||
build(:site_membership, user: build(:user)),
|
||||
build(:site_membership, user: build(:user))
|
||||
]
|
||||
)
|
||||
|
||||
conn = post(conn, Routes.billing_path(conn, :change_plan, @v4_growth_plan))
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~
|
||||
"Unable to subscribe to this plan because the following limits are exceeded: [:team_member_limit]"
|
||||
end
|
||||
|
||||
test "calls Paddle API to update subscription", %{conn: conn, user: user} do
|
||||
insert(:subscription, user: user)
|
||||
|
||||
|
@ -37,7 +37,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
|
||||
assert doc =~ "Upgrade your account"
|
||||
assert doc =~ "You have used <b>0</b>\nbillable pageviews in the last 30 days"
|
||||
assert doc =~ "You have used"
|
||||
assert doc =~ "<b>0</b>"
|
||||
assert doc =~ "billable pageviews in the last 30 days"
|
||||
assert doc =~ "Questions?"
|
||||
assert doc =~ "What happens if I go over my page views limit?"
|
||||
assert doc =~ "Enterprise"
|
||||
@ -200,6 +202,33 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
|
||||
assert get_paddle_checkout_params(find(doc, @business_checkout_button))["product"] ==
|
||||
@v4_business_5m_monthly_plan_id
|
||||
end
|
||||
|
||||
test "checkout is disabled when pageview usage exceeds rendered plan limit", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
site = insert(:site, members: [user])
|
||||
generate_usage_for(site, 10_001)
|
||||
|
||||
{:ok, lv, _doc} = get_liveview(conn)
|
||||
|
||||
doc = lv |> element(@slider_input) |> render_change(%{slider: 0})
|
||||
|
||||
assert text_of_element(doc, @growth_plan_box) =~ "Your usage exceeds this plan"
|
||||
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
|
||||
assert text_of_element(doc, @business_plan_box) =~ "Your usage exceeds this plan"
|
||||
assert class_of_element(doc, @business_checkout_button) =~ "pointer-events-none"
|
||||
end
|
||||
|
||||
test "warns about losing access to a feature", %{conn: conn, user: user} do
|
||||
site = insert(:site, members: [user])
|
||||
Plausible.Props.allow(site, ["author"])
|
||||
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
|
||||
assert text_of_attr(find(doc, @growth_checkout_button), "onclick") =~
|
||||
"if (confirm(\"This plan does not support Custom Properties, which you are currently using. Please note that by subscribing to this plan you will lose access to this feature.\")) {Paddle.Checkout.open"
|
||||
end
|
||||
end
|
||||
|
||||
describe "for a user with a v4 growth subscription plan" do
|
||||
@ -257,7 +286,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
|
||||
])
|
||||
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
assert doc =~ "You have used <b>2</b>\nbillable pageviews in the last 30 days"
|
||||
assert doc =~ "You have used"
|
||||
assert doc =~ "<b>2</b>"
|
||||
assert doc =~ "billable pageviews in the last 30 days"
|
||||
end
|
||||
|
||||
test "gets default selected interval from current subscription plan", %{conn: conn} do
|
||||
@ -373,6 +404,49 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
|
||||
assert text_of_element(doc, @business_checkout_button) == "Downgrade"
|
||||
assert text_of_element(doc, @growth_checkout_button) == "Downgrade to Growth"
|
||||
end
|
||||
|
||||
test "checkout is disabled when team member usage exceeds rendered plan limit", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
insert(:site,
|
||||
memberships: [
|
||||
build(:site_membership, user: user, role: :owner),
|
||||
build(:site_membership, user: build(:user)),
|
||||
build(:site_membership, user: build(:user)),
|
||||
build(:site_membership, user: build(:user))
|
||||
]
|
||||
)
|
||||
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
|
||||
assert text_of_element(doc, @growth_plan_box) =~ "Your usage exceeds this plan"
|
||||
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
|
||||
end
|
||||
|
||||
test "checkout is disabled when sites usage exceeds rendered plan limit", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
for _ <- 1..11, do: insert(:site, members: [user])
|
||||
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
|
||||
assert text_of_element(doc, @growth_plan_box) =~ "Your usage exceeds this plan"
|
||||
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
|
||||
end
|
||||
|
||||
test "warns about losing access to a feature", %{conn: conn, user: user} do
|
||||
site = insert(:site, members: [user])
|
||||
Plausible.Props.allow(site, ["author"])
|
||||
|
||||
insert(:goal, currency: :USD, site: site, event_name: "Purchase")
|
||||
|
||||
{:ok, _lv, doc} = get_liveview(conn)
|
||||
|
||||
assert text_of_attr(find(doc, @growth_checkout_button), "onclick") =~
|
||||
"if (!confirm(\"This plan does not support Custom Properties and Revenue Goals, which you are currently using. Please note that by subscribing to this plan you will lose access to these features.\")) {e.preventDefault()}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "for a user with a v3 business (unlimited team members) subscription plan" do
|
||||
|
@ -139,6 +139,12 @@ defmodule Plausible.TestUtils do
|
||||
|> Plug.Conn.fetch_session()
|
||||
end
|
||||
|
||||
def generate_usage_for(site, i) do
|
||||
events = for _i <- 1..i, do: Factory.build(:pageview)
|
||||
populate_stats(site, events)
|
||||
:ok
|
||||
end
|
||||
|
||||
def populate_stats(site, events) do
|
||||
Enum.map(events, fn event ->
|
||||
case event do
|
||||
|
Loading…
Reference in New Issue
Block a user