mirror of
https://github.com/plausible/analytics.git
synced 2024-11-26 00:24:44 +03:00
Refactor Plausible.Billing.Plans module (#3268)
This pull request introduces a series of improvements to Plausible.Billing.Plans, including: * Tag the JSON file with the plan version * Rename the JSON field limit to monthly_pageview_limit * Move site_limit function to Billing.Plans * Refactor subscription_interval, allowance and site_limit functions * Remove unused AnalyzePlans task
This commit is contained in:
parent
3f9ca35d58
commit
34f1ddfc8c
@ -8,7 +8,6 @@ ENVIRONMENT=test
|
||||
MAILER_ADAPTER=Bamboo.TestAdapter
|
||||
ENABLE_EMAIL_VERIFICATION=true
|
||||
SELFHOST=false
|
||||
SITE_LIMIT=3
|
||||
HCAPTCHA_SITEKEY=test
|
||||
HCAPTCHA_SECRET=scottiger
|
||||
IP_GEOLOCATION_DB=test/priv/GeoLite2-City-Test.mmdb
|
||||
|
@ -204,11 +204,6 @@ custom_script_name =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("CUSTOM_SCRIPT_NAME", "script")
|
||||
|
||||
{site_limit, ""} =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("SITE_LIMIT", "50")
|
||||
|> Integer.parse()
|
||||
|
||||
site_limit_exempt =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("SITE_LIMIT_EXEMPT", "")
|
||||
@ -241,7 +236,6 @@ config :plausible,
|
||||
environment: env,
|
||||
mailer_email: mailer_email,
|
||||
super_admin_user_ids: super_admin_user_ids,
|
||||
site_limit: site_limit,
|
||||
site_limit_exempt: site_limit_exempt,
|
||||
is_selfhost: is_selfhost,
|
||||
custom_script_name: custom_script_name,
|
||||
|
@ -1,60 +0,0 @@
|
||||
defmodule Mix.Tasks.AnalyzePlans do
|
||||
use Mix.Task
|
||||
use Plausible.Repo
|
||||
|
||||
# coveralls-ignore-start
|
||||
|
||||
def run(_) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
res =
|
||||
Repo.all(
|
||||
from s in Plausible.Billing.Subscription,
|
||||
where: s.status == "active",
|
||||
group_by: s.paddle_plan_id,
|
||||
select: {s.paddle_plan_id, count(s)}
|
||||
)
|
||||
|
||||
res =
|
||||
Enum.map(res, fn {plan_id, count} ->
|
||||
plan = Plausible.Billing.Plans.find(plan_id)
|
||||
|
||||
if plan do
|
||||
is_monthly = plan_id == plan.monthly_product_id
|
||||
|
||||
monthly_revenue =
|
||||
if is_monthly do
|
||||
price(plan.monthly_cost)
|
||||
else
|
||||
price(plan.yearly_cost) / 12
|
||||
end
|
||||
|
||||
{PlausibleWeb.StatsView.large_number_format(plan.limit), monthly_revenue, count}
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
res =
|
||||
Enum.reduce(res, %{}, fn {limit, revenue, count}, acc ->
|
||||
total_revenue = revenue * count
|
||||
|
||||
Map.update(acc, limit, {total_revenue, count}, fn {ex_rev, ex_count} ->
|
||||
{ex_rev + total_revenue, ex_count + count}
|
||||
end)
|
||||
end)
|
||||
|
||||
total_revenue = round(Enum.reduce(res, 0, fn {_, {revenue, _}}, sum -> sum + revenue end))
|
||||
|
||||
for {limit, {rev, _}} <- res do
|
||||
percentage = round(rev / total_revenue * 100)
|
||||
|
||||
IO.puts(
|
||||
"The #{limit} plan makes up #{percentage}% of total revenue ($#{round(rev)} / $#{total_revenue})"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp price("$" <> nr) do
|
||||
String.to_integer(nr)
|
||||
end
|
||||
end
|
@ -156,37 +156,6 @@ defmodule Plausible.Billing do
|
||||
Plausible.Stats.Clickhouse.usage_breakdown(site_ids)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of sites that an account is allowed to have. Accounts for
|
||||
grandfathering old accounts to unlimited websites and ignores site limit on self-hosted
|
||||
installations.
|
||||
"""
|
||||
@limit_accounts_since ~D[2021-05-05]
|
||||
def sites_limit(user) do
|
||||
user = Plausible.Repo.preload(user, :enterprise_plan)
|
||||
|
||||
cond do
|
||||
Timex.before?(user.inserted_at, @limit_accounts_since) ->
|
||||
nil
|
||||
|
||||
Application.get_env(:plausible, :is_selfhost) ->
|
||||
nil
|
||||
|
||||
user.email in Application.get_env(:plausible, :site_limit_exempt) ->
|
||||
nil
|
||||
|
||||
user.enterprise_plan ->
|
||||
if has_active_enterprise_subscription(user) do
|
||||
nil
|
||||
else
|
||||
Application.get_env(:plausible, :site_limit)
|
||||
end
|
||||
|
||||
true ->
|
||||
Application.get_env(:plausible, :site_limit)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_subscription_created(params) do
|
||||
params =
|
||||
if present?(params["passthrough"]) do
|
||||
@ -255,18 +224,6 @@ defmodule Plausible.Billing do
|
||||
end
|
||||
end
|
||||
|
||||
defp has_active_enterprise_subscription(user) do
|
||||
Plausible.Repo.exists?(
|
||||
from(s in Plausible.Billing.Subscription,
|
||||
join: e in Plausible.Billing.EnterprisePlan,
|
||||
on: s.user_id == e.user_id and s.paddle_plan_id == e.paddle_plan_id,
|
||||
where: s.user_id == ^user.id,
|
||||
where: s.paddle_plan_id == ^user.enterprise_plan.paddle_plan_id,
|
||||
where: s.status == "active"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp format_subscription(params) do
|
||||
%{
|
||||
paddle_subscription_id: params["subscription_id"],
|
||||
|
@ -2,12 +2,14 @@ defmodule Plausible.Billing.Plan do
|
||||
@moduledoc false
|
||||
|
||||
@derive Jason.Encoder
|
||||
@enforce_keys ~w(limit volume monthly_cost yearly_cost monthly_product_id yearly_product_id)a
|
||||
@enforce_keys ~w(kind site_limit monthly_pageview_limit volume monthly_cost yearly_cost monthly_product_id yearly_product_id)a
|
||||
defstruct @enforce_keys
|
||||
|
||||
@type t() ::
|
||||
%__MODULE__{
|
||||
limit: non_neg_integer(),
|
||||
kind: atom(),
|
||||
monthly_pageview_limit: non_neg_integer(),
|
||||
site_limit: non_neg_integer(),
|
||||
volume: String.t(),
|
||||
monthly_cost: String.t() | nil,
|
||||
yearly_cost: String.t() | nil,
|
||||
@ -34,7 +36,14 @@ defmodule Plausible.Billing.Plans do
|
||||
path
|
||||
|> File.read!()
|
||||
|> Jason.decode!(keys: :atoms!)
|
||||
|> Enum.map(&Map.put(&1, :volume, PlausibleWeb.StatsView.large_number_format(&1.limit)))
|
||||
|> Enum.map(
|
||||
&Map.put(
|
||||
&1,
|
||||
:volume,
|
||||
PlausibleWeb.StatsView.large_number_format(&1.monthly_pageview_limit)
|
||||
)
|
||||
)
|
||||
|> Enum.map(&Map.put(&1, :kind, String.to_atom(&1.kind)))
|
||||
|> Enum.map(&struct!(Plausible.Billing.Plan, &1))
|
||||
|
||||
Module.put_attribute(__MODULE__, f, contents)
|
||||
@ -48,7 +57,7 @@ defmodule Plausible.Billing.Plans do
|
||||
As new versions of plans are introduced, users who were on old plans can
|
||||
still choose from old plans.
|
||||
"""
|
||||
def for_user(user) do
|
||||
def for_user(%Plausible.Auth.User{} = user) do
|
||||
user = Plausible.Users.with_subscription(user)
|
||||
|
||||
cond do
|
||||
@ -96,47 +105,82 @@ defmodule Plausible.Billing.Plans do
|
||||
end)
|
||||
end
|
||||
|
||||
def subscription_interval(%Plausible.Billing.Subscription{paddle_plan_id: "free_10k"}),
|
||||
do: "N/A"
|
||||
@limit_sites_since ~D[2021-05-05]
|
||||
@spec site_limit(Plausible.Auth.User.t()) :: non_neg_integer() | :unlimited
|
||||
@doc """
|
||||
Returns the limit of sites a user can have.
|
||||
|
||||
For enterprise customers, returns :unlimited. The site limit is checked in a
|
||||
background job so as to avoid service disruption.
|
||||
"""
|
||||
def site_limit(user) do
|
||||
cond do
|
||||
Application.get_env(:plausible, :is_selfhost) -> :unlimited
|
||||
user.email in Application.get_env(:plausible, :site_limit_exempt) -> :unlimited
|
||||
Timex.before?(user.inserted_at, @limit_sites_since) -> :unlimited
|
||||
true -> get_site_limit_from_plan(user)
|
||||
end
|
||||
end
|
||||
|
||||
@site_limit_for_trials 50
|
||||
@site_limit_for_free_10k 50
|
||||
defp get_site_limit_from_plan(user) do
|
||||
user = Plausible.Users.with_subscription(user)
|
||||
|
||||
case get_subscription_plan(user.subscription) do
|
||||
%Plausible.Billing.EnterprisePlan{} -> :unlimited
|
||||
%Plausible.Billing.Plan{site_limit: site_limit} -> site_limit
|
||||
:free_10k -> @site_limit_for_free_10k
|
||||
nil -> @site_limit_for_trials
|
||||
end
|
||||
end
|
||||
|
||||
defp get_subscription_plan(subscription) do
|
||||
if subscription && subscription.paddle_plan_id == "free_10k" do
|
||||
:free_10k
|
||||
else
|
||||
find(subscription) || get_enterprise_plan(subscription)
|
||||
end
|
||||
end
|
||||
|
||||
def subscription_interval(subscription) do
|
||||
case find(subscription.paddle_plan_id) do
|
||||
nil ->
|
||||
enterprise_plan = get_enterprise_plan(subscription)
|
||||
case get_subscription_plan(subscription) do
|
||||
%Plausible.Billing.EnterprisePlan{billing_interval: interval} ->
|
||||
interval
|
||||
|
||||
enterprise_plan && enterprise_plan.billing_interval
|
||||
|
||||
plan ->
|
||||
if subscription.paddle_plan_id == plan.monthly_product_id do
|
||||
%Plausible.Billing.Plan{} = plan ->
|
||||
if plan.monthly_product_id == subscription.paddle_plan_id do
|
||||
"monthly"
|
||||
else
|
||||
"yearly"
|
||||
end
|
||||
|
||||
_any ->
|
||||
"N/A"
|
||||
end
|
||||
end
|
||||
|
||||
def allowance(%Plausible.Billing.Subscription{paddle_plan_id: "free_10k"}), do: 10_000
|
||||
|
||||
@spec allowance(Plausible.Billing.Subscription.t()) :: non_neg_integer() | nil
|
||||
def allowance(subscription) do
|
||||
found = find(subscription.paddle_plan_id)
|
||||
case get_subscription_plan(subscription) do
|
||||
%Plausible.Billing.EnterprisePlan{monthly_pageview_limit: limit} ->
|
||||
limit
|
||||
|
||||
if found do
|
||||
Map.fetch!(found, :limit)
|
||||
else
|
||||
enterprise_plan = get_enterprise_plan(subscription)
|
||||
%Plausible.Billing.Plan{monthly_pageview_limit: limit} ->
|
||||
limit
|
||||
|
||||
if enterprise_plan do
|
||||
enterprise_plan.monthly_pageview_limit
|
||||
else
|
||||
:free_10k ->
|
||||
10_000
|
||||
|
||||
_any ->
|
||||
Sentry.capture_message("Unknown allowance for plan",
|
||||
extra: %{
|
||||
paddle_plan_id: subscription.paddle_plan_id
|
||||
}
|
||||
extra: %{paddle_plan_id: subscription.paddle_plan_id}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_enterprise_plan(nil), do: nil
|
||||
|
||||
defp get_enterprise_plan(%Plausible.Billing.Subscription{} = subscription) do
|
||||
Repo.get_by(Plausible.Billing.EnterprisePlan,
|
||||
user_id: subscription.user_id,
|
||||
@ -147,21 +191,21 @@ defmodule Plausible.Billing.Plans do
|
||||
@enterprise_level_usage 10_000_000
|
||||
@spec suggest(Plausible.Auth.User.t(), non_neg_integer()) :: Plausible.Billing.Plan.t()
|
||||
@doc """
|
||||
Returns the most appropriate plan for a user based on their usage during a
|
||||
Returns the most appropriate plan for a user based on their usage during a
|
||||
given cycle.
|
||||
|
||||
If the usage during the cycle exceeds the enterprise-level threshold, or if
|
||||
the user already belongs to an enterprise plan, it suggests the :enterprise
|
||||
If the usage during the cycle exceeds the enterprise-level threshold, or if
|
||||
the user already belongs to an enterprise plan, it suggests the :enterprise
|
||||
plan.
|
||||
|
||||
Otherwise, it recommends the plan where the cycle usage falls just under the
|
||||
Otherwise, it recommends the plan where the cycle usage falls just under the
|
||||
plan's limit from the available options for the user.
|
||||
"""
|
||||
def suggest(user, usage_during_cycle) do
|
||||
cond do
|
||||
usage_during_cycle > @enterprise_level_usage -> :enterprise
|
||||
Plausible.Auth.enterprise?(user) -> :enterprise
|
||||
true -> Enum.find(for_user(user), &(usage_during_cycle < &1.limit))
|
||||
true -> Enum.find(for_user(user), &(usage_during_cycle < &1.monthly_pageview_limit))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -13,13 +13,10 @@ defmodule Plausible.Sites do
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.run(:limit, fn _, _ ->
|
||||
limit = Plausible.Billing.sites_limit(user)
|
||||
count = owned_sites_count(user)
|
||||
|
||||
if count >= limit do
|
||||
{:error, limit}
|
||||
else
|
||||
{:ok, count}
|
||||
case {Plausible.Billing.Plans.site_limit(user), owned_sites_count(user)} do
|
||||
{:unlimited, actual} -> {:ok, actual}
|
||||
{limit, actual} when actual >= limit -> {:error, limit}
|
||||
{_limit, actual} -> {:ok, actual}
|
||||
end
|
||||
end)
|
||||
|> Ecto.Multi.insert(:site, site_changeset)
|
||||
|
@ -51,17 +51,17 @@ defmodule PlausibleWeb.SiteController do
|
||||
|
||||
def new(conn, _params) do
|
||||
current_user = conn.assigns[:current_user]
|
||||
|
||||
owned_site_count = Plausible.Sites.owned_sites_count(current_user)
|
||||
site_limit = Plausible.Billing.sites_limit(current_user)
|
||||
is_at_limit = site_limit && owned_site_count >= site_limit
|
||||
is_first_site = owned_site_count == 0
|
||||
|
||||
changeset = Plausible.Site.changeset(%Plausible.Site{})
|
||||
{site_limit, is_at_limit} =
|
||||
case Plausible.Billing.Plans.site_limit(current_user) do
|
||||
:unlimited -> {:unlimited, false}
|
||||
limit when is_integer(limit) -> {limit, owned_site_count >= limit}
|
||||
end
|
||||
|
||||
render(conn, "new.html",
|
||||
changeset: changeset,
|
||||
is_first_site: is_first_site,
|
||||
changeset: Plausible.Site.changeset(%Plausible.Site{}),
|
||||
is_first_site: owned_site_count == 0,
|
||||
is_at_limit: is_at_limit,
|
||||
site_limit: site_limit,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
|
@ -1,72 +1,92 @@
|
||||
[
|
||||
{
|
||||
"limit":10000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000,
|
||||
"monthly_cost":"$6",
|
||||
"monthly_product_id":"558018",
|
||||
"yearly_cost":"$48",
|
||||
"yearly_product_id":"572810"
|
||||
"yearly_product_id":"572810",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":100000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":100000,
|
||||
"monthly_cost":"$12",
|
||||
"monthly_product_id":"558745",
|
||||
"yearly_cost":"$96",
|
||||
"yearly_product_id":"590752"
|
||||
"yearly_product_id":"590752",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":200000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":200000,
|
||||
"monthly_cost":"$18",
|
||||
"monthly_product_id":"597485",
|
||||
"yearly_cost":"$144",
|
||||
"yearly_product_id":"597486"
|
||||
"yearly_product_id":"597486",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":500000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":500000,
|
||||
"monthly_cost":"$27",
|
||||
"monthly_product_id":"597487",
|
||||
"yearly_cost":"$216",
|
||||
"yearly_product_id":"597488"
|
||||
"yearly_product_id":"597488",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":1000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":1000000,
|
||||
"monthly_cost":"$48",
|
||||
"monthly_product_id":"597642",
|
||||
"yearly_cost":"$384",
|
||||
"yearly_product_id":"597643"
|
||||
"yearly_product_id":"597643",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":2000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":2000000,
|
||||
"monthly_cost":"$69",
|
||||
"monthly_product_id":"597309",
|
||||
"yearly_cost":"$552",
|
||||
"yearly_product_id":"597310"
|
||||
"yearly_product_id":"597310",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":5000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":5000000,
|
||||
"monthly_cost":"$99",
|
||||
"monthly_product_id":"597311",
|
||||
"yearly_cost":"$792",
|
||||
"yearly_product_id":"597312"
|
||||
"yearly_product_id":"597312",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":10000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000000,
|
||||
"monthly_cost":"$150",
|
||||
"monthly_product_id":"642352",
|
||||
"yearly_cost":"$1200",
|
||||
"yearly_product_id":"642354"
|
||||
"yearly_product_id":"642354",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":20000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":20000000,
|
||||
"monthly_cost":"$225",
|
||||
"monthly_product_id":"642355",
|
||||
"yearly_cost":"$1800",
|
||||
"yearly_product_id":"642356"
|
||||
"yearly_product_id":"642356",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":50000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":50000000,
|
||||
"monthly_cost":"$330",
|
||||
"monthly_product_id":"650652",
|
||||
"yearly_cost":"$2640",
|
||||
"yearly_product_id":"650653"
|
||||
"yearly_product_id":"650653",
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -1,72 +1,92 @@
|
||||
[
|
||||
{
|
||||
"limit":10000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000,
|
||||
"monthly_cost":"$6",
|
||||
"monthly_product_id":"654177",
|
||||
"yearly_cost":"$60",
|
||||
"yearly_product_id":"653232"
|
||||
"yearly_product_id":"653232",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":100000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":100000,
|
||||
"monthly_cost":"$12",
|
||||
"monthly_product_id":"654178",
|
||||
"yearly_cost":"$120",
|
||||
"yearly_product_id":"653234"
|
||||
"yearly_product_id":"653234",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":200000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":200000,
|
||||
"monthly_cost":"$20",
|
||||
"monthly_product_id":"653237",
|
||||
"yearly_cost":"$200",
|
||||
"yearly_product_id":"653236"
|
||||
"yearly_product_id":"653236",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":500000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":500000,
|
||||
"monthly_cost":"$30",
|
||||
"monthly_product_id":"653238",
|
||||
"yearly_cost":"$300",
|
||||
"yearly_product_id":"653239"
|
||||
"yearly_product_id":"653239",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":1000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":1000000,
|
||||
"monthly_cost":"$50",
|
||||
"monthly_product_id":"653240",
|
||||
"yearly_cost":"$500",
|
||||
"yearly_product_id":"653242"
|
||||
"yearly_product_id":"653242",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":2000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":2000000,
|
||||
"monthly_cost":"$70",
|
||||
"monthly_product_id":"653253",
|
||||
"yearly_cost":"$700",
|
||||
"yearly_product_id":"653254"
|
||||
"yearly_product_id":"653254",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":5000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":5000000,
|
||||
"monthly_cost":"$100",
|
||||
"monthly_product_id":"653255",
|
||||
"yearly_cost":"$1000",
|
||||
"yearly_product_id":"653256"
|
||||
"yearly_product_id":"653256",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":10000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000000,
|
||||
"monthly_cost":"$150",
|
||||
"monthly_product_id":"654181",
|
||||
"yearly_cost":"$1500",
|
||||
"yearly_product_id":"653257"
|
||||
"yearly_product_id":"653257",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":20000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":20000000,
|
||||
"monthly_cost":"$225",
|
||||
"monthly_product_id":"654182",
|
||||
"yearly_cost":"$2250",
|
||||
"yearly_product_id":"653258"
|
||||
"yearly_product_id":"653258",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":50000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":50000000,
|
||||
"monthly_cost":"$330",
|
||||
"monthly_product_id":"654183",
|
||||
"yearly_cost":"$3300",
|
||||
"yearly_product_id":"653259"
|
||||
"yearly_product_id":"653259",
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -1,59 +1,74 @@
|
||||
[
|
||||
{
|
||||
"limit":10000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000,
|
||||
"monthly_cost":"$9",
|
||||
"monthly_product_id":"749342",
|
||||
"yearly_cost":"$90",
|
||||
"yearly_product_id":"749343"
|
||||
"yearly_product_id":"749343",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":100000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":100000,
|
||||
"monthly_cost":"$19",
|
||||
"monthly_product_id":"749344",
|
||||
"yearly_cost":"$190",
|
||||
"yearly_product_id":"749345"
|
||||
"yearly_product_id":"749345",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":200000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":200000,
|
||||
"monthly_cost":"$29",
|
||||
"monthly_product_id":"749346",
|
||||
"yearly_cost":"$290",
|
||||
"yearly_product_id":"749347"
|
||||
"yearly_product_id":"749347",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":500000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":500000,
|
||||
"monthly_cost":"$49",
|
||||
"monthly_product_id":"749348",
|
||||
"yearly_cost":"$490",
|
||||
"yearly_product_id":"749349"
|
||||
"yearly_product_id":"749349",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":1000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":1000000,
|
||||
"monthly_cost":"$69",
|
||||
"monthly_product_id":"749350",
|
||||
"yearly_cost":"$690",
|
||||
"yearly_product_id":"749352"
|
||||
"yearly_product_id":"749352",
|
||||
"site_limit":50
|
||||
},
|
||||
|
||||
{
|
||||
"limit":2000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":2000000,
|
||||
"monthly_cost":"$89",
|
||||
"monthly_product_id":"749353",
|
||||
"yearly_cost":"$890",
|
||||
"yearly_product_id":"749355"
|
||||
"yearly_product_id":"749355",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":5000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":5000000,
|
||||
"monthly_cost":"$129",
|
||||
"monthly_product_id":"749356",
|
||||
"yearly_cost":"$1290",
|
||||
"yearly_product_id":"749357"
|
||||
"yearly_product_id":"749357",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":10000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000000,
|
||||
"monthly_cost":"$169",
|
||||
"monthly_product_id":"749358",
|
||||
"yearly_cost":"$1690",
|
||||
"yearly_product_id":"749359"
|
||||
"yearly_product_id":"749359",
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -1,16 +1,20 @@
|
||||
[
|
||||
{
|
||||
"limit":10000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000,
|
||||
"monthly_product_id":"19878",
|
||||
"yearly_product_id":"20127",
|
||||
"monthly_cost":"$6",
|
||||
"yearly_cost":"$60"
|
||||
"yearly_cost":"$60",
|
||||
"site_limit":50
|
||||
},
|
||||
{
|
||||
"limit":100000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":100000,
|
||||
"monthly_product_id":"20657",
|
||||
"yearly_product_id":"20658",
|
||||
"monthly_cost":"$12.34",
|
||||
"yearly_cost":"$120.34"
|
||||
"yearly_cost":"$120.34",
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -1,9 +1,11 @@
|
||||
[
|
||||
{
|
||||
"limit":150000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":150000000,
|
||||
"yearly_product_id":"648089",
|
||||
"yearly_cost":"$4800",
|
||||
"monthly_product_id":null,
|
||||
"monthly_cost":null
|
||||
"monthly_cost":null,
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -1,9 +1,11 @@
|
||||
[
|
||||
{
|
||||
"limit":10000000,
|
||||
"kind":"growth",
|
||||
"monthly_pageview_limit":10000000,
|
||||
"monthly_product_id":"655350",
|
||||
"monthly_cost":"$250",
|
||||
"yearly_product_id":null,
|
||||
"yearly_cost":null
|
||||
"yearly_cost":null,
|
||||
"site_limit":50
|
||||
}
|
||||
]
|
||||
|
@ -49,52 +49,6 @@ defmodule Plausible.BillingTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "sites_limit" do
|
||||
test "is the globally configured site limit for regular accounts" do
|
||||
user = insert(:user, subscription: build(:subscription))
|
||||
|
||||
assert Billing.sites_limit(user) == Application.get_env(:plausible, :site_limit)
|
||||
end
|
||||
|
||||
test "is limited for enterprise customers who have not upgraded yet" do
|
||||
enterprise_plan_paddle_id = "123321"
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: enterprise_plan_paddle_id),
|
||||
subscription: build(:subscription, paddle_plan_id: "99999")
|
||||
)
|
||||
|
||||
assert Billing.sites_limit(user) == Application.get_env(:plausible, :site_limit)
|
||||
end
|
||||
|
||||
test "is unlimited for enterprise customers. Their site limit is checked in a background job so as to avoid service disruption" do
|
||||
enterprise_plan_paddle_id = "123321"
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: enterprise_plan_paddle_id),
|
||||
subscription: build(:subscription, paddle_plan_id: enterprise_plan_paddle_id)
|
||||
)
|
||||
|
||||
assert Billing.sites_limit(user) == nil
|
||||
end
|
||||
|
||||
test "is unlimited for enterprise customers who are due to change a plan" do
|
||||
enterprise_plan_paddle_id = "123321"
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: enterprise_plan_paddle_id),
|
||||
subscription: build(:subscription, paddle_plan_id: enterprise_plan_paddle_id)
|
||||
)
|
||||
|
||||
insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id")
|
||||
|
||||
assert Billing.sites_limit(user) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "last_two_billing_cycles" do
|
||||
test "billing on the 1st" do
|
||||
last_bill_date = ~D[2021-01-01]
|
||||
|
@ -88,22 +88,22 @@ defmodule Plausible.Billing.PlansTest do
|
||||
user = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_plan_id))
|
||||
|
||||
assert %Plausible.Billing.Plan{
|
||||
limit: 100_000,
|
||||
monthly_pageview_limit: 100_000,
|
||||
monthly_cost: "$12",
|
||||
monthly_product_id: "558745",
|
||||
volume: "100k",
|
||||
yearly_cost: "$96",
|
||||
yearly_product_id: "590752"
|
||||
} == Plans.suggest(user, 10_000)
|
||||
} = Plans.suggest(user, 10_000)
|
||||
|
||||
assert %Plausible.Billing.Plan{
|
||||
limit: 200_000,
|
||||
monthly_pageview_limit: 200_000,
|
||||
monthly_cost: "$18",
|
||||
monthly_product_id: "597485",
|
||||
volume: "200k",
|
||||
yearly_cost: "$144",
|
||||
yearly_product_id: "597486"
|
||||
} == Plans.suggest(user, 100_000)
|
||||
} = Plans.suggest(user, 100_000)
|
||||
end
|
||||
|
||||
test "returns nil when user has enterprise-level usage" do
|
||||
@ -153,4 +153,86 @@ defmodule Plausible.Billing.PlansTest do
|
||||
] == Plans.yearly_product_ids()
|
||||
end
|
||||
end
|
||||
|
||||
describe "site_limit/1" do
|
||||
test "returns 50 when user is on an old plan" do
|
||||
user_on_v1 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_plan_id))
|
||||
user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id))
|
||||
user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id))
|
||||
|
||||
assert 50 == Plans.site_limit(user_on_v1)
|
||||
assert 50 == Plans.site_limit(user_on_v2)
|
||||
assert 50 == Plans.site_limit(user_on_v3)
|
||||
end
|
||||
|
||||
test "returns 50 when user is on free_10k plan" do
|
||||
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
|
||||
assert 50 == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "returns unlimited when user is on an enterprise plan" do
|
||||
user = insert(:user)
|
||||
|
||||
enterprise_plan =
|
||||
insert(:enterprise_plan,
|
||||
user_id: user.id,
|
||||
monthly_pageview_limit: 100_000,
|
||||
site_limit: 500
|
||||
)
|
||||
|
||||
_subscription =
|
||||
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
|
||||
|
||||
assert :unlimited == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "returns 50 when user in on trial" do
|
||||
user = insert(:user, trial_expiry_date: Timex.shift(Timex.now(), days: 7))
|
||||
assert 50 == Plans.site_limit(user)
|
||||
|
||||
user = insert(:user, trial_expiry_date: Timex.shift(Timex.now(), days: -7))
|
||||
assert 50 == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "returns the subscription limit for enterprise users who have not paid yet" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
subscription: build(:subscription, paddle_plan_id: @v1_plan_id)
|
||||
)
|
||||
|
||||
assert 50 == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "returns 50 for enterprise users who have not upgraded yet and are on trial" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
subscription: nil
|
||||
)
|
||||
|
||||
assert 50 == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "is unlimited for enterprise customers" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
subscription: build(:subscription, paddle_plan_id: "123321")
|
||||
)
|
||||
|
||||
assert :unlimited == Plans.site_limit(user)
|
||||
end
|
||||
|
||||
test "is unlimited for enterprise customers who are due to change a plan" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "old-paddle-plan-id"),
|
||||
subscription: build(:subscription, paddle_plan_id: "old-paddle-plan-id")
|
||||
)
|
||||
|
||||
insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id")
|
||||
assert :unlimited == Plans.site_limit(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -62,10 +62,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
|
||||
end
|
||||
|
||||
test "does not allow creating more sites than the limit", %{conn: conn, user: user} do
|
||||
patch_env(:site_limit, 3)
|
||||
insert(:site, members: [user])
|
||||
insert(:site, members: [user])
|
||||
insert(:site, members: [user])
|
||||
insert_list(50, :site, members: [user])
|
||||
|
||||
conn =
|
||||
post(conn, "/api/v1/sites", %{
|
||||
@ -75,7 +72,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
|
||||
|
||||
assert json_response(conn, 403) == %{
|
||||
"error" =>
|
||||
"Your account has reached the limit of 3 sites per account. Please contact hello@plausible.io to unlock more sites."
|
||||
"Your account has reached the limit of 50 sites per account. Please contact hello@plausible.io to unlock more sites."
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -170,10 +170,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
# default site limit defined in config/.test.env
|
||||
insert(:site, members: [user])
|
||||
insert(:site, members: [user])
|
||||
insert(:site, members: [user])
|
||||
insert_list(50, :site, members: [user])
|
||||
|
||||
conn =
|
||||
post(conn, "/sites", %{
|
||||
@ -185,7 +182,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
||||
|
||||
assert html = html_response(conn, 200)
|
||||
assert html =~ "Upgrade required"
|
||||
assert html =~ "Your account is limited to 3 sites"
|
||||
assert html =~ "Your account is limited to 50 sites"
|
||||
assert html =~ "Please contact support"
|
||||
refute Repo.get_by(Plausible.Site, domain: "over-limit.example.com")
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user