Clean up old upgrade page (dead code) (#5652)

* remove deprecated modules/fns

* get rid of starter_tier flag

* remove legacy? arg from plans
This commit is contained in:
RobertJoonas 2025-08-19 10:12:48 +01:00 committed by GitHub
parent e3bef74cde
commit 17675af4d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 20 additions and 2258 deletions

View File

@ -32,32 +32,30 @@ defmodule Plausible.Billing.Plans do
end end
end end
defp starter_plans_for(subscription, legacy?) do defp starter_plans_for(subscription) do
active_plan = get_regular_plan(subscription, only_non_expired: true) active_plan = get_regular_plan(subscription, only_non_expired: true)
case {legacy?, active_plan} do case active_plan do
{true, _} -> [] %Plan{kind: :growth, generation: g} when g <= 4 -> []
{_, %Plan{kind: :growth, generation: g}} when g <= 4 -> [] _ -> Enum.filter(plans_v5(), &(&1.kind == :starter))
{_, _} -> Enum.filter(plans_v5(), &(&1.kind == :starter))
end end
end end
@spec growth_plans_for(Subscription.t(), boolean()) :: [Plan.t()] @spec growth_plans_for(Subscription.t()) :: [Plan.t()]
@doc """ @doc """
Returns a list of growth plans available for the subscription to choose. Returns a list of growth plans available for the subscription to choose.
As new versions of plans are introduced, subscriptions which were on old plans can As new versions of plans are introduced, subscriptions which were on old plans can
still choose from old plans. still choose from old plans.
""" """
def growth_plans_for(subscription, legacy? \\ false) do def growth_plans_for(subscription) do
owned_plan = get_regular_plan(subscription) owned_plan = get_regular_plan(subscription)
latest = plans_v5()
default_plans = if legacy?, do: plans_v4(), else: plans_v5()
cond do cond do
is_nil(owned_plan) -> default_plans is_nil(owned_plan) -> latest
subscription && Subscriptions.expired?(subscription) -> default_plans subscription && Subscriptions.expired?(subscription) -> latest
owned_plan.kind == :business -> default_plans owned_plan.kind == :business -> latest
owned_plan.generation == 1 -> plans_v1() |> drop_high_plans(owned_plan) owned_plan.generation == 1 -> plans_v1() |> drop_high_plans(owned_plan)
owned_plan.generation == 2 -> plans_v2() |> drop_high_plans(owned_plan) owned_plan.generation == 2 -> plans_v2() |> drop_high_plans(owned_plan)
owned_plan.generation == 3 -> plans_v3() owned_plan.generation == 3 -> plans_v3()
@ -67,27 +65,24 @@ defmodule Plausible.Billing.Plans do
|> Enum.filter(&(&1.kind == :growth)) |> Enum.filter(&(&1.kind == :growth))
end end
def business_plans_for(subscription, legacy? \\ false) do def business_plans_for(subscription) do
owned_plan = get_regular_plan(subscription) owned_plan = get_regular_plan(subscription)
latest = plans_v5()
default_plans = if legacy?, do: plans_v4(), else: plans_v5()
cond do cond do
subscription && Subscriptions.expired?(subscription) -> default_plans subscription && Subscriptions.expired?(subscription) -> latest
owned_plan && owned_plan.generation <= 3 -> plans_v3() owned_plan && owned_plan.generation <= 3 -> plans_v3()
owned_plan && owned_plan.generation <= 4 -> plans_v4() owned_plan && owned_plan.generation <= 4 -> plans_v4()
true -> default_plans true -> latest
end end
|> Enum.filter(&(&1.kind == :business)) |> Enum.filter(&(&1.kind == :business))
end end
def available_plans_for(subscription, opts \\ []) do def available_plans_for(subscription, opts \\ []) do
legacy? = Keyword.get(opts, :legacy?, false)
%{ %{
starter: starter_plans_for(subscription, legacy?) |> maybe_add_prices(opts), starter: starter_plans_for(subscription) |> maybe_add_prices(opts),
growth: growth_plans_for(subscription, legacy?) |> maybe_add_prices(opts), growth: growth_plans_for(subscription) |> maybe_add_prices(opts),
business: business_plans_for(subscription, legacy?) |> maybe_add_prices(opts) business: business_plans_for(subscription) |> maybe_add_prices(opts)
} }
end end

View File

@ -82,19 +82,6 @@ defmodule Plausible.Billing.Quota do
end end
end end
@doc """
[DEPRECATED] Used in LegacyChoosePlan in order to suggest a tier
when `starter_tier` flag is not enabled.
"""
def legacy_suggest_tier(usage, highest_growth, highest_business, owned_tier) do
cond do
not eligible_for_upgrade?(usage) -> nil
usage_fits_plan?(usage, highest_growth) and owned_tier != :business -> :growth
usage_fits_plan?(usage, highest_business) -> :business
true -> :custom
end
end
defp usage_fits_plan?(usage, plan) do defp usage_fits_plan?(usage, plan) do
with :ok <- ensure_within_plan_limits(usage, plan), with :ok <- ensure_within_plan_limits(usage, plan),
:ok <- ensure_feature_access(usage, plan) do :ok <- ensure_feature_access(usage, plan) do

View File

@ -21,42 +21,6 @@ defmodule Plausible.Teams.Billing do
@typep last_30_days_usage() :: %{:last_30_days => Quota.usage_cycle()} @typep last_30_days_usage() :: %{:last_30_days => Quota.usage_cycle()}
@typep monthly_pageview_usage() :: Quota.cycles_usage() | last_30_days_usage() @typep monthly_pageview_usage() :: Quota.cycles_usage() | last_30_days_usage()
@starter_tier_launch ~D[2025-06-11]
def starter_tier_launch(), do: @starter_tier_launch
def show_new_upgrade_page?(nil = _team) do
FunWithFlags.enabled?(:starter_tier)
end
def show_new_upgrade_page?(%Teams.Team{} = team) do
team = Teams.with_subscription(team)
feature_flag_enabled? = FunWithFlags.enabled?(:starter_tier, for: team)
subscription_plan = Plans.get_subscription_plan(team.subscription)
case {subscription_plan, team.trial_expiry_date} do
{%Plan{generation: 5}, _} ->
true
{nil, nil} ->
feature_flag_enabled?
{nil, trial_expiry_date} ->
diff = Date.diff(@starter_tier_launch, trial_expiry_date)
# Active or recently (less than 10 days ago) ended trials
# should be able to subscribe to the plans they saw when
# they signed up.
if diff <= 10 and diff >= -30 do
false
else
feature_flag_enabled?
end
{_, _} ->
feature_flag_enabled?
end
end
def grandfathered_team?(nil), do: false def grandfathered_team?(nil), do: false
def grandfathered_team?(team) do def grandfathered_team?(team) do

View File

@ -1,141 +0,0 @@
defmodule PlausibleWeb.Components.Billing.LegacyPlanBenefits do
@moduledoc """
[DEPRECATED] This file is essentially a copy of
`PlausibleWeb.Components.Billing.PlanBenefits` with the
intent of keeping the old behaviour in place for the users without
the `starter_tier` feature flag enabled.
"""
use Phoenix.Component
alias Plausible.Billing.Plan
attr :benefits, :list, required: true
attr :class, :string, default: nil
@doc """
This function takes a list of benefits returned by either one of:
* `for_growth/1`
* `for_business/2`
* `for_enterprise/1`.
and renders them as HTML.
The benefits in the given list can be either strings or functions
returning a Phoenix component. This allows, for example, to render
links within the plan benefit text.
"""
def render(assigns) do
~H"""
<ul role="list" class={["mt-8 space-y-3 text-sm leading-6 xl:mt-10", @class]}>
<li :for={benefit <- @benefits} class="flex gap-x-3">
<Heroicons.check class="h-6 w-5 text-indigo-600 dark:text-green-600" />
{if is_binary(benefit), do: benefit, else: benefit.(assigns)}
</li>
</ul>
"""
end
@doc """
This function takes a growth plan and returns a list representing
the different benefits a user gets when subscribing to this plan.
"""
def for_growth(plan) do
[
team_member_limit_benefit(plan),
site_limit_benefit(plan),
data_retention_benefit(plan),
"Intuitive, fast and privacy-friendly dashboard",
"Email/Slack reports",
"Google Analytics import"
]
|> Kernel.++(feature_benefits(plan))
|> Kernel.++(["Saved Segments"])
|> Enum.filter(& &1)
end
@doc """
Returns Business benefits for the given Business plan.
A second argument is also required - list of Growth benefits. This
is because we don't want to list the same benefits in both Growth
and Business. Everything in Growth is also included in Business.
"""
def for_business(plan, growth_benefits) do
[
"Everything in Growth",
team_member_limit_benefit(plan),
site_limit_benefit(plan),
data_retention_benefit(plan)
]
|> Kernel.++(feature_benefits(plan))
|> Kernel.--(growth_benefits)
|> Kernel.++(["Priority support"])
|> Enum.filter(& &1)
end
@doc """
This function only takes a list of business benefits. Since all
limits and features of enterprise plans are configurable, we can
say on the upgrade page that enterprise plans include everything
in Business.
"""
def for_enterprise(business_benefits) do
team_members =
if "Up to 10 team members" in business_benefits, do: "10+ team members"
data_retention =
if "5 years of data retention" in business_benefits, do: "5+ years of data retention"
[
"Everything in Business",
team_members,
"50+ sites",
"600+ Stats API requests per hour",
&sites_api_benefit/1,
"Single Sign-On (SSO)",
data_retention
]
|> Enum.filter(& &1)
end
defp data_retention_benefit(%Plan{} = plan) do
if plan.data_retention_in_years, do: "#{plan.data_retention_in_years} years of data retention"
end
defp team_member_limit_benefit(%Plan{} = plan) do
case plan.team_member_limit do
:unlimited -> "Unlimited team members"
number -> "Up to #{number} team members"
end
end
defp site_limit_benefit(%Plan{} = plan), do: "Up to #{plan.site_limit} sites"
defp feature_benefits(%Plan{} = plan) do
Enum.flat_map(plan.features, fn feature_mod ->
case feature_mod.name() do
:goals -> ["Goals and custom events"]
:teams -> []
:shared_links -> []
:stats_api -> ["Stats API (600 requests per hour)", "Looker Studio Connector"]
:revenue_goals -> ["Ecommerce revenue attribution"]
_ -> [feature_mod.display_name()]
end
end)
end
defp sites_api_benefit(assigns) do
~H"""
<p>
Sites API access for
<.link
class="text-indigo-500 hover:text-indigo-400"
href="https://plausible.io/white-label-web-analytics"
>
reselling
</.link>
</p>
"""
end
end

View File

@ -1,378 +0,0 @@
defmodule PlausibleWeb.Components.Billing.LegacyPlanBox do
@moduledoc """
[DEPRECATED] This file is essentially a copy of
`PlausibleWeb.Components.Billing.PlanBox` with the
intent of keeping the old behaviour in place for the users without
the `starter_tier` feature flag enabled.
"""
use PlausibleWeb, :component
require Plausible.Billing.Subscription.Status
alias PlausibleWeb.Components.Billing.{LegacyPlanBenefits, Notice}
alias Plausible.Billing.{Plan, Quota, Subscription}
def standard(assigns) do
highlight =
cond do
assigns.owned && assigns.recommended -> "Current"
assigns.recommended -> "Recommended"
true -> nil
end
assigns = assign(assigns, :highlight, highlight)
~H"""
<div
id={"#{@kind}-plan-box"}
class={[
"shadow-lg bg-white rounded-3xl px-6 sm:px-8 py-4 sm:py-6 dark:bg-gray-800",
!@highlight && "dark:ring-gray-600",
@highlight && "ring-2 ring-indigo-600 dark:ring-indigo-300"
]}
>
<div class="flex items-center justify-between gap-x-4">
<h3 class={[
"text-lg font-semibold leading-8",
!@highlight && "text-gray-900 dark:text-gray-100",
@highlight && "text-indigo-600 dark:text-indigo-300"
]}>
{String.capitalize(to_string(@kind))}
</h3>
<.pill :if={@highlight} text={@highlight} />
</div>
<div>
<.render_price_info available={@available} {assigns} />
<%= if @available do %>
<.checkout id={"#{@kind}-checkout"} {assigns} />
<% else %>
<.contact_button class="bg-indigo-600 hover:bg-indigo-500 text-white" />
<% end %>
</div>
<%= if @owned && @kind == :growth && @plan_to_render.generation < 4 do %>
<Notice.growth_grandfathered />
<% else %>
<LegacyPlanBenefits.render benefits={@benefits} class="text-gray-600 dark:text-gray-100" />
<% end %>
</div>
"""
end
def enterprise(assigns) do
~H"""
<div
id="enterprise-plan-box"
class={[
"rounded-3xl px-6 sm:px-8 py-4 sm:py-6 bg-gray-900 shadow-xl dark:bg-gray-800",
!@recommended && "dark:ring-gray-600",
@recommended && "ring-4 ring-indigo-500 dark:ring-2 dark:ring-indigo-300"
]}
>
<div class="flex items-center justify-between gap-x-4">
<h3 class={[
"text-lg font-semibold leading-8",
!@recommended && "text-white dark:text-gray-100",
@recommended && "text-indigo-400 dark:text-indigo-300"
]}>
Enterprise
</h3>
<span
:if={@recommended}
id="enterprise-highlight-pill"
class="rounded-full ring-1 ring-indigo-500 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-400 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50"
>
Recommended
</span>
</div>
<p class="mt-6 flex items-baseline gap-x-1">
<span class="text-4xl font-bold tracking-tight text-white dark:text-gray-100">
Custom
</span>
</p>
<p class="h-4 mt-1"></p>
<.contact_button class="" />
<LegacyPlanBenefits.render benefits={@benefits} class="text-gray-300 dark:text-gray-100" />
</div>
"""
end
defp pill(assigns) do
~H"""
<div class="flex items-center justify-between gap-x-4">
<p
id="highlight-pill"
class="rounded-full bg-indigo-600/10 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-600 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50"
>
{@text}
</p>
</div>
"""
end
defp render_price_info(%{available: false} = assigns) do
~H"""
<p id={"#{@kind}-custom-price"} class="mt-6 flex items-baseline gap-x-1">
<span class="text-4xl font-bold tracking-tight text-gray-900 dark:text-white">
Custom
</span>
</p>
<p class="h-4 mt-1"></p>
"""
end
defp render_price_info(assigns) do
~H"""
<p class="mt-6 flex items-baseline gap-x-1">
<.price_tag
kind={@kind}
selected_interval={@selected_interval}
plan_to_render={@plan_to_render}
/>
</p>
<p class="mt-1 text-xs">+ VAT if applicable</p>
"""
end
defp price_tag(%{plan_to_render: %Plan{monthly_cost: nil}} = assigns) do
~H"""
<span class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100">
N/A
</span>
"""
end
defp price_tag(%{selected_interval: :monthly} = assigns) do
~H"""
<span
id={"#{@kind}-price-tag-amount"}
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
>
{@plan_to_render.monthly_cost |> Plausible.Billing.format_price()}
</span>
<span
id={"#{@kind}-price-tag-interval"}
class="text-sm font-semibold leading-6 text-gray-600 dark:text-gray-500"
>
/month
</span>
"""
end
defp price_tag(%{selected_interval: :yearly} = assigns) do
~H"""
<span class="text-2xl font-bold w-max tracking-tight line-through text-gray-500 dark:text-gray-600 mr-1">
{@plan_to_render.monthly_cost |> Money.mult!(12) |> Plausible.Billing.format_price()}
</span>
<span
id={"#{@kind}-price-tag-amount"}
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
>
{@plan_to_render.yearly_cost |> Plausible.Billing.format_price()}
</span>
<span id={"#{@kind}-price-tag-interval"} class="text-sm font-semibold leading-6 text-gray-600">
/year
</span>
"""
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)
subscription =
Plausible.Teams.Billing.get_subscription(assigns.current_team)
billing_details_expired =
Subscription.Status.in?(subscription, [
Subscription.Status.paused(),
Subscription.Status.past_due()
])
subscription_deleted = Subscription.Status.deleted?(subscription)
usage_check = check_usage_within_plan_limits(assigns)
{checkout_disabled, disabled_message} =
cond do
not Quota.eligible_for_upgrade?(assigns.usage) ->
{true, nil}
change_plan_link_text == "Currently on this plan" && not subscription_deleted ->
{true, nil}
usage_check != :ok ->
{true, "Your usage exceeds this plan"}
billing_details_expired ->
{true, "Please update your billing details first"}
true ->
{false, nil}
end
exceeded_plan_limits =
case usage_check do
{:error, {:over_plan_limits, limits}} ->
limits
_ ->
[]
end
feature_usage_check = Quota.ensure_feature_access(assigns.usage, assigns.plan_to_render)
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(:exceeded_plan_limits, exceeded_plan_limits)
|> assign(:confirm_message, losing_features_message(feature_usage_check))
~H"""
<%= if @owned_plan && Plausible.Billing.Subscriptions.resumable?(@current_team.subscription) do %>
<.change_plan_link {assigns} />
<% else %>
<PlausibleWeb.Components.Billing.paddle_button
user={@current_user}
team={@current_team}
{assigns}
>
Upgrade
</PlausibleWeb.Components.Billing.paddle_button>
<% end %>
<.tooltip :if={@exceeded_plan_limits != [] && @disabled_message}>
<div class="pt-2 text-sm w-full flex items-center text-red-700 dark:text-red-500 justify-center">
{@disabled_message}
<Heroicons.information_circle class="hidden sm:block w-5 h-5 sm:ml-2" />
</div>
<:tooltip_content>
Your usage exceeds the following limit(s):<br /><br />
<p :for={limit <- @exceeded_plan_limits}>
{Phoenix.Naming.humanize(limit)}<br />
</p>
</:tooltip_content>
</.tooltip>
<div
:if={@disabled_message && @exceeded_plan_limits == []}
class="pt-2 text-sm w-full text-red-700 dark:text-red-500 text-center"
>
{@disabled_message}
</div>
"""
end
defp check_usage_within_plan_limits(%{available: false}) do
{:error, :plan_unavailable}
end
defp check_usage_within_plan_limits(%{
available: true,
usage: usage,
current_team: current_team,
plan_to_render: plan
}) do
# At this point, the user is *not guaranteed* to have a team,
# with ongoing trial.
trial_active_or_ended_recently? =
not is_nil(current_team) and not is_nil(current_team.trial_expiry_date) and
Plausible.Teams.trial_days_left(current_team) >= -10
limit_checking_opts =
cond do
current_team && current_team.allow_next_upgrade_override ->
[ignore_pageview_limit: true]
trial_active_or_ended_recently? && plan.volume == "10k" ->
[pageview_allowance_margin: 0.3]
trial_active_or_ended_recently? ->
[pageview_allowance_margin: 0.15]
true ->
[]
end
Quota.ensure_within_plan_limits(usage, plan, limit_checking_opts)
end
defp get_paddle_product_id(%Plan{monthly_product_id: plan_id}, :monthly), do: plan_id
defp get_paddle_product_id(%Plan{yearly_product_id: plan_id}, :yearly), do: plan_id
defp change_plan_link_text(
%{
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 ->
"Downgrade to Growth"
from_kind == :growth && to_kind == :business ->
"Upgrade to Business"
from_volume == to_volume && from_interval == to_interval ->
"Currently on this plan"
from_volume == to_volume ->
"Change billing interval"
from_volume > to_volume ->
"Downgrade"
true ->
"Upgrade"
end
end
defp change_plan_link_text(_), do: nil
defp change_plan_link(assigns) do
confirmed =
if assigns.confirm_message, do: "confirm(\"#{assigns.confirm_message}\")", else: "true"
assigns = assign(assigns, :confirmed, confirmed)
~H"""
<button
id={"#{@kind}-checkout"}
onclick={"if (#{@confirmed}) {window.location = '#{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",
!@checkout_disabled && "bg-indigo-600 hover:bg-indigo-500",
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
]}
>
{@change_plan_link_text}
</button>
"""
end
defp losing_features_message(:ok), do: nil
defp losing_features_message({:error, {:unavailable_features, features}}) do
features_list_str =
features
|> Enum.map(fn feature_mod -> feature_mod.display_name() end)
|> PlausibleWeb.TextHelpers.pretty_join()
"This plan does not support #{features_list_str}, which you have been using. By subscribing to this plan, you will not have access to #{if length(features) == 1, do: "this feature", else: "these features"}."
end
defp contact_button(assigns) do
~H"""
<.link
href="https://plausible.io/contact"
class={[
"mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 bg-gray-800 hover:bg-gray-700 text-white dark:bg-indigo-600 dark:hover:bg-indigo-500",
@class
]}
>
Contact us
</.link>
"""
end
end

View File

@ -283,8 +283,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
defp lose_grandfathering_warning(%{subscription: subscription} = assigns) do defp lose_grandfathering_warning(%{subscription: subscription} = assigns) do
plan = Plans.get_regular_plan(subscription, only_non_expired: true) plan = Plans.get_regular_plan(subscription, only_non_expired: true)
latest_generation = if FunWithFlags.enabled?(:starter_tier), do: 5, else: 4 loses_grandfathering? = plan && plan.generation < 5
loses_grandfathering? = plan && plan.generation < latest_generation
assigns = assign(assigns, :loses_grandfathering?, loses_grandfathering?) assigns = assign(assigns, :loses_grandfathering?, loses_grandfathering?)

View File

@ -19,19 +19,12 @@ defmodule PlausibleWeb.BillingController do
def choose_plan(conn, _params) do def choose_plan(conn, _params) do
team = conn.assigns.current_team team = conn.assigns.current_team
{live_module, hide_header?} =
if Plausible.Teams.Billing.show_new_upgrade_page?(team) do
{PlausibleWeb.Live.ChoosePlan, true}
else
{PlausibleWeb.Live.LegacyChoosePlan, false}
end
if Plausible.Teams.Billing.enterprise_configured?(team) do if Plausible.Teams.Billing.enterprise_configured?(team) do
redirect(conn, to: Routes.billing_path(conn, :upgrade_to_enterprise_plan)) redirect(conn, to: Routes.billing_path(conn, :upgrade_to_enterprise_plan))
else else
render(conn, "choose_plan.html", render(conn, "choose_plan.html",
live_module: live_module, live_module: PlausibleWeb.Live.ChoosePlan,
hide_header?: hide_header?, hide_header?: true,
disable_global_notices?: true, disable_global_notices?: true,
skip_plausible_tracking: true, skip_plausible_tracking: true,
connect_live_socket: true connect_live_socket: true

View File

@ -1,321 +0,0 @@
defmodule PlausibleWeb.Live.LegacyChoosePlan do
@moduledoc """
[DEPRECATED] This file is essentially a copy of
`PlausibleWeb.Live.ChoosePlan` with the
intent of keeping the old behaviour in place for the users without
the `starter_tier` feature flag enabled.
"""
use PlausibleWeb, :live_view
require Plausible.Billing.Subscription.Status
alias PlausibleWeb.Components.Billing.{
LegacyPlanBox,
LegacyPlanBenefits,
Notice,
PageviewSlider
}
alias Plausible.Billing.{Plans, Quota}
@contact_link "https://plausible.io/contact"
@billing_faq_link "https://plausible.io/docs/billing"
def mount(_params, %{"remote_ip" => remote_ip}, socket) do
socket =
socket
|> assign_new(:pending_ownership_site_ids, fn %{current_user: current_user} ->
Plausible.Teams.Memberships.all_pending_site_transfers(current_user.email)
end)
|> assign_new(:usage, fn %{
current_team: current_team,
pending_ownership_site_ids: pending_ownership_site_ids
} ->
Plausible.Teams.Billing.quota_usage(current_team,
with_features: true,
pending_ownership_site_ids: pending_ownership_site_ids
)
end)
|> assign_new(:subscription, fn %{current_team: current_team} ->
Plausible.Teams.Billing.get_subscription(current_team)
end)
|> assign_new(:owned_plan, fn %{subscription: subscription} ->
Plans.get_regular_plan(subscription, only_non_expired: true)
end)
|> assign_new(:owned_tier, fn %{owned_plan: owned_plan} ->
if owned_plan, do: Map.get(owned_plan, :kind), else: nil
end)
|> assign_new(:current_interval, fn %{subscription: subscription} ->
current_user_subscription_interval(subscription)
end)
|> assign_new(:available_plans, fn %{subscription: subscription} ->
Plans.available_plans_for(subscription,
with_prices: true,
customer_ip: remote_ip,
legacy?: true
)
end)
|> assign_new(:recommended_tier, fn %{
usage: usage,
available_plans: available_plans,
owned_tier: owned_tier
} ->
highest_growth_plan = List.last(available_plans.growth)
highest_business_plan = List.last(available_plans.business)
Quota.legacy_suggest_tier(usage, highest_growth_plan, highest_business_plan, owned_tier)
end)
|> assign_new(:available_volumes, fn %{available_plans: available_plans} ->
get_available_volumes(available_plans)
end)
|> assign_new(:selected_volume, fn %{
usage: usage,
available_volumes: available_volumes
} ->
default_selected_volume(usage.monthly_pageviews, available_volumes)
end)
|> assign_new(:selected_interval, fn %{current_interval: current_interval} ->
current_interval || :monthly
end)
|> assign_new(:selected_growth_plan, fn %{
available_plans: available_plans,
selected_volume: selected_volume
} ->
get_plan_by_volume(available_plans.growth, selected_volume)
end)
|> assign_new(:selected_business_plan, fn %{
available_plans: available_plans,
selected_volume: selected_volume
} ->
get_plan_by_volume(available_plans.business, selected_volume)
end)
{:ok, socket}
end
def render(assigns) do
growth_plan_to_render =
assigns.selected_growth_plan || List.last(assigns.available_plans.growth)
business_plan_to_render =
assigns.selected_business_plan || List.last(assigns.available_plans.business)
growth_benefits =
LegacyPlanBenefits.for_growth(growth_plan_to_render)
business_benefits =
LegacyPlanBenefits.for_business(business_plan_to_render, growth_benefits)
enterprise_benefits = LegacyPlanBenefits.for_enterprise(business_benefits)
assigns =
assigns
|> assign(:growth_plan_to_render, growth_plan_to_render)
|> assign(:business_plan_to_render, business_plan_to_render)
|> assign(:growth_benefits, growth_benefits)
|> assign(:business_benefits, business_benefits)
|> assign(:enterprise_benefits, enterprise_benefits)
~H"""
<div class="pt-1 pb-12 sm:pb-16 text-gray-900 dark:text-gray-100">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<Notice.pending_site_ownerships_notice
class="pb-6"
pending_ownership_count={length(@pending_ownership_site_ids)}
/>
<Notice.subscription_past_due class="pb-6" subscription={@subscription} />
<Notice.subscription_paused class="pb-6" subscription={@subscription} />
<Notice.upgrade_ineligible :if={not Quota.eligible_for_upgrade?(@usage)} />
<div class="mx-auto max-w-4xl text-center">
<p class="text-4xl font-bold tracking-tight lg:text-5xl">
{if @owned_plan,
do: "Change subscription plan",
else: "Upgrade your account"}
</p>
</div>
<div class="mt-12 flex flex-col gap-8 lg:flex-row items-center lg:items-baseline">
<.interval_picker selected_interval={@selected_interval} />
<PageviewSlider.render
selected_volume={@selected_volume}
available_volumes={@available_volumes}
/>
</div>
<div class="mt-6 isolate mx-auto grid max-w-md grid-cols-1 gap-8 lg:mx-0 lg:max-w-none lg:grid-cols-3">
<LegacyPlanBox.standard
kind={:growth}
owned={@owned_tier == :growth}
recommended={@recommended_tier == :growth}
plan_to_render={@growth_plan_to_render}
benefits={@growth_benefits}
available={!!@selected_growth_plan}
{assigns}
/>
<LegacyPlanBox.standard
kind={:business}
owned={@owned_tier == :business}
recommended={@recommended_tier == :business}
plan_to_render={@business_plan_to_render}
benefits={@business_benefits}
available={!!@selected_business_plan}
{assigns}
/>
<LegacyPlanBox.enterprise
benefits={@enterprise_benefits}
recommended={@recommended_tier == :custom}
/>
</div>
<p class="mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400">
<.render_usage pageview_usage={@usage.monthly_pageviews} />
</p>
<.pageview_limit_notice :if={!@owned_plan} />
<.help_links />
</div>
</div>
<PlausibleWeb.Components.Billing.paddle_script />
"""
end
defp render_usage(assigns) do
case assigns.pageview_usage do
%{last_30_days: _} ->
~H"""
You have used
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_30_days.total) %></b> billable pageviews in the last 30 days
"""
%{last_cycle: _} ->
~H"""
You have used
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_cycle.total) %></b> billable pageviews in the last billing cycle
"""
end
end
def handle_event("set_interval", %{"interval" => interval}, socket) do
new_interval =
case interval do
"yearly" -> :yearly
"monthly" -> :monthly
end
{:noreply, assign(socket, selected_interval: new_interval)}
end
def handle_event("slide", %{"slider" => index}, socket) do
index = String.to_integer(index)
%{available_plans: available_plans, available_volumes: available_volumes} = socket.assigns
new_volume =
if index == length(available_volumes) do
:enterprise
else
Enum.at(available_volumes, index)
end
{:noreply,
assign(socket,
selected_volume: new_volume,
selected_growth_plan: get_plan_by_volume(available_plans.growth, new_volume),
selected_business_plan: get_plan_by_volume(available_plans.business, new_volume)
)}
end
defp default_selected_volume(pageview_usage, available_volumes) do
total =
case pageview_usage do
%{last_30_days: usage} -> usage.total
%{last_cycle: usage} -> usage.total
end
Enum.find(available_volumes, &(total < &1)) || :enterprise
end
defp current_user_subscription_interval(subscription) do
case Plans.subscription_interval(subscription) do
"yearly" -> :yearly
"monthly" -> :monthly
_ -> nil
end
end
defp get_plan_by_volume(_, :enterprise), do: nil
defp get_plan_by_volume(plans, volume) do
Enum.find(plans, &(&1.monthly_pageview_limit == volume))
end
defp interval_picker(assigns) do
~H"""
<div class="lg:flex-1 lg:order-3 lg:justify-end flex">
<div class="relative">
<.two_months_free />
<fieldset class="grid grid-cols-2 gap-x-1 rounded-full bg-white dark:bg-gray-700 p-1 text-center text-sm font-semibold leading-5 shadow dark:ring-gray-600">
<label
class={"cursor-pointer rounded-full px-2.5 py-1 text-gray-900 dark:text-white #{if @selected_interval == :monthly, do: "bg-indigo-600 text-white"}"}
phx-click="set_interval"
phx-value-interval="monthly"
>
<input type="radio" name="frequency" value="monthly" class="sr-only" />
<span>Monthly</span>
</label>
<label
class={"cursor-pointer rounded-full px-2.5 py-1 text-gray-900 dark:text-white #{if @selected_interval == :yearly, do: "bg-indigo-600 text-white"}"}
phx-click="set_interval"
phx-value-interval="yearly"
>
<input type="radio" name="frequency" value="yearly" class="sr-only" />
<span>Yearly</span>
</label>
</fieldset>
</div>
</div>
"""
end
def two_months_free(assigns) do
~H"""
<span class="absolute -right-5 -top-4 whitespace-no-wrap w-max px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-yellow-100 border border-yellow-300 text-yellow-700">
2 months free
</span>
"""
end
defp pageview_limit_notice(assigns) do
~H"""
<div class="mt-12 mx-auto mt-6 max-w-2xl">
<dt>
<p class="w-full text-center text-gray-900 dark:text-gray-100">
<span class="text-center font-semibold leading-7">
What happens if I go over my page views limit?
</span>
</p>
</dt>
<dd class="mt-3">
<div class="text-justify leading-7 block text-gray-600 dark:text-gray-100">
You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly. If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point.
</div>
</dd>
</div>
"""
end
defp help_links(assigns) do
~H"""
<div class="mt-8 text-center">
Questions? <a class="text-indigo-600" href={contact_link()}>Contact us</a>
or see <a class="text-indigo-600" href={billing_faq_link()}>billing FAQ</a>
</div>
"""
end
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)
(growth_volumes ++ business_volumes)
|> Enum.uniq()
end
defp contact_link(), do: @contact_link
defp billing_faq_link(), do: @billing_faq_link
end

View File

@ -13,8 +13,6 @@ use Plausible
import Plausible.Teams.Test import Plausible.Teams.Test
FunWithFlags.enable(:starter_tier)
words = words =
for i <- 0..(:erlang.system_info(:atom_count) - 1), for i <- 0..(:erlang.system_info(:atom_count) - 1),
do: :erlang.binary_to_term(<<131, 75, i::24>>) do: :erlang.binary_to_term(<<131, 75, i::24>>)

View File

@ -622,77 +622,4 @@ defmodule Plausible.BillingTest do
refute Plausible.Teams.Billing.has_active_subscription?(paused_team) refute Plausible.Teams.Billing.has_active_subscription?(paused_team)
refute Plausible.Teams.Billing.has_active_subscription?(nil) refute Plausible.Teams.Billing.has_active_subscription?(nil)
end end
@v4_plan_id "857088"
@v5_plan_id "910429"
describe "show_new_upgrade_page?/1" do
@describetag :ee_only
test "returns true for non-existing team" do
assert Plausible.Teams.Billing.show_new_upgrade_page?(nil) == true
end
test "returns true for team with no trial_expiry_date" do
{:ok, team} = new_user() |> Plausible.Teams.get_or_create()
team = %{team | trial_expiry_date: nil}
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
end
test "returns true for a user already on a v5 plan regardless of starter_tier feature flag" do
team =
new_user()
|> subscribe_to_plan(@v5_plan_id)
|> team_of()
FunWithFlags.disable(:starter_tier, for_actor: team)
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
end
test "returns false for a trial that expired 10 days before starter tier release" do
trial_expiry_date =
Plausible.Teams.Billing.starter_tier_launch()
|> Date.shift(day: -10)
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == false
end
test "returns true for a trial that expired 11 days before starter tier release" do
trial_expiry_date =
Plausible.Teams.Billing.starter_tier_launch()
|> Date.shift(day: -11)
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
end
test "returns false for a trial that started on the day of starter tier release" do
trial_expiry_date =
Plausible.Teams.Billing.starter_tier_launch()
|> Date.shift(day: 30)
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == false
end
test "returns true for a trial that started after starter tier release" do
trial_expiry_date =
Plausible.Teams.Billing.starter_tier_launch()
|> Date.shift(day: 31)
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
end
test "returns true for a v4 subscription" do
team =
new_user(trial_expiry_date: ~D[2025-01-01])
|> subscribe_to_plan(@v4_plan_id)
|> team_of()
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
end
end
end end

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ Application.ensure_all_started(:double)
FunWithFlags.enable(:channels) FunWithFlags.enable(:channels)
FunWithFlags.enable(:scroll_depth) FunWithFlags.enable(:scroll_depth)
FunWithFlags.enable(:starter_tier)
Ecto.Adapters.SQL.Sandbox.mode(Plausible.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(Plausible.Repo, :manual)