From 885b1d271a92b275e06da4d0f9b68b6f33fbf4d7 Mon Sep 17 00:00:00 2001 From: Uku Taht Date: Thu, 6 May 2021 11:46:22 +0300 Subject: [PATCH] Refactor current plan data format to make changing it easier --- assets/css/app.css | 2 + lib/plausible/billing/plans.ex | 169 +++++++++--------- lib/plausible_web/email.ex | 10 +- .../templates/billing/_plan_option.html.eex | 4 +- .../templates/billing/change_plan.html.eex | 42 +++-- .../templates/billing/upgrade.html.eex | 44 +++-- .../templates/email/over_limit.html.eex | 2 +- .../email/trial_upgrade_email.html.eex | 2 +- lib/plausible_web/views/auth_view.ex | 2 +- lib/workers/check_usage.ex | 4 +- lib/workers/notify_annual_renewal.ex | 2 +- priv/plans_v1.json | 0 test/plausible/billing/plans_test.exs | 8 - .../controllers/auth_controller_test.exs | 14 ++ test/workers/check_usage_test.exs | 5 +- test/workers/notify_annual_renewal_test.exs | 5 +- 16 files changed, 185 insertions(+), 130 deletions(-) create mode 100644 priv/plans_v1.json diff --git a/assets/css/app.css b/assets/css/app.css index 59cabe882..b68003678 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -7,6 +7,8 @@ @import "tooltip.css"; @import "flatpickr.css"; +[x-cloak] { display: none; } + .button { @apply inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md leading-5 transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500; } diff --git a/lib/plausible/billing/plans.ex b/lib/plausible/billing/plans.ex index e10b09d88..619c2a9a2 100644 --- a/lib/plausible/billing/plans.ex +++ b/lib/plausible/billing/plans.ex @@ -1,80 +1,93 @@ defmodule Plausible.Billing.Plans do - @monthly_plans [ - %{product_id: "558018", cost: "$6", limit: 10_000, cycle: "monthly"}, - %{product_id: "558745", cost: "$12", limit: 100_000, cycle: "monthly"}, - %{product_id: "597485", cost: "$18", limit: 200_000, cycle: "monthly"}, - %{product_id: "597487", cost: "$27", limit: 500_000, cycle: "monthly"}, - %{product_id: "597642", cost: "$48", limit: 1_000_000, cycle: "monthly"}, - %{product_id: "597309", cost: "$69", limit: 2_000_000, cycle: "monthly"}, - %{product_id: "597311", cost: "$99", limit: 5_000_000, cycle: "monthly"}, - %{product_id: "642352", cost: "$150", limit: 10_000_000, cycle: "monthly"}, - %{product_id: "642355", cost: "$225", limit: 20_000_000, cycle: "monthly"}, - %{product_id: "650652", cost: "$330", limit: 50_000_000, cycle: "monthly"} - ] - - @yearly_plans [ - %{product_id: "572810", cost: "$48", monthly_cost: "$4", limit: 10_000, cycle: "yearly"}, - %{product_id: "590752", cost: "$96", monthly_cost: "$8", limit: 100_000, cycle: "yearly"}, - %{product_id: "597486", cost: "$144", monthly_cost: "$12", limit: 200_000, cycle: "yearly"}, - %{product_id: "597488", cost: "$216", monthly_cost: "$18", limit: 500_000, cycle: "yearly"}, - %{product_id: "597643", cost: "$384", monthly_cost: "$32", limit: 1_000_000, cycle: "yearly"}, - %{product_id: "597310", cost: "$552", monthly_cost: "$46", limit: 2_000_000, cycle: "yearly"}, - %{product_id: "597312", cost: "$792", monthly_cost: "$66", limit: 5_000_000, cycle: "yearly"}, + @plans_v1 [ + %{ + limit: 10_000, + monthly_product_id: "558018", + monthly_cost: "$6", + yearly_product_id: "572810", + yearly_cost: "$48" + }, + %{ + limit: 100_000, + monthly_product_id: "558745", + monthly_cost: "$12", + yearly_product_id: "590752", + yearly_cost: "$96" + }, + %{ + limit: 200_000, + monthly_product_id: "597485", + monthly_cost: "$18", + yearly_product_id: "597486", + yearly_cost: "$144" + }, + %{ + limit: 500_000, + monthly_product_id: "597487", + monthly_cost: "$27", + yearly_product_id: "597488", + yearly_cost: "$216" + }, + %{ + limit: 1_000_000, + monthly_product_id: "597642", + monthly_cost: "$48", + yearly_product_id: "597643", + yearly_cost: "$384" + }, + %{ + limit: 2_000_000, + monthly_product_id: "597309", + monthly_cost: "$69", + yearly_product_id: "597310", + yearly_cost: "$552" + }, + %{ + limit: 5_000_000, + monthly_product_id: "597311", + monthly_cost: "$99", + yearly_product_id: "597312", + yearly_cost: "$792" + }, %{ - product_id: "642354", - cost: "$1200", - monthly_cost: "$100", limit: 10_000_000, - cycle: "yearly" - }, - %{ - product_id: "642356", - cost: "$1800", + monthly_product_id: "642352", monthly_cost: "$150", + yearly_product_id: "642354", + yearly_cost: "$1200" + }, + %{ limit: 20_000_000, - cycle: "yearly" + monthly_product_id: "642355", + monthly_cost: "$225", + yearly_product_id: "642356", + yearly_cost: "$1800" }, %{ - product_id: "650653", - cost: "$2640", - monthly_cost: "$220", limit: 50_000_000, - cycle: "yearly" - }, - %{ - product_id: "648089", - cost: "$4800", - monthly_cost: "$400", - limit: 150_000_000, - cycle: "yearly" + monthly_product_id: "650652", + monthly_cost: "$330", + yearly_product_id: "650653", + yearly_cost: "$2640" } ] - @all_plans @monthly_plans ++ @yearly_plans + @yearly_plans_v1 [ + %{limit: 150_000_000, yearly_product_id: "648089", yearly_cost: "$4800"} + ] - def plans do - monthly = - @monthly_plans - |> Enum.map(fn plan -> {String.to_atom(number_format(plan[:limit])), plan} end) - |> Enum.into(%{}) - - yearly = - @yearly_plans - |> Enum.map(fn plan -> {String.to_atom(number_format(plan[:limit])), plan} end) - |> Enum.into(%{}) - - %{ - monthly: monthly, - yearly: yearly - } + def plans_for(user) do + @plans_v1 |> Enum.map(fn plan -> Map.put(plan, :volume, number_format(plan[:limit])) end) end - def yearly_plan_ids do - Enum.map(@yearly_plans, fn plan -> plan[:product_id] end) + def all_yearly_plan_ids do + Enum.map(@plans_v1, fn plan -> plan[:yearly_product_id] end) end def for_product_id(product_id) do - Enum.find(@all_plans, fn plan -> plan[:product_id] == product_id end) + Enum.find(@plans_v1, fn plan -> + product_id in [plan[:monthly_product_id], plan[:yearly_product_id]] + end) end def subscription_quota("free_10k"), do: "10k" @@ -90,38 +103,32 @@ defmodule Plausible.Billing.Plans do def subscription_interval(product_id) do case for_product_id(product_id) do - nil -> raise "Unknown interval for subscription #{product_id}" - product -> product[:cycle] + nil -> + raise "Unknown interval for subscription #{product_id}" + + plan -> + if product_id == plan[:monthly_product_id] do + "monthly" + else + "yearly" + end end end - def suggested_plan_name(usage) do - plan = suggested_plan(usage) - number_format(plan[:limit]) <> "/mo" - end - - def suggested_plan_cost(usage) do - plan = suggested_plan(usage) - plan[:cost] <> "/mo" - end - - def suggested_plan_cost_yearly(usage) do - plan = Enum.find(@yearly_plans, fn plan -> usage < plan[:limit] end) - plan[:monthly_cost] <> "/mo" - end - - defp suggested_plan(usage) do - Enum.find(@monthly_plans, fn plan -> usage < plan[:limit] end) - end + def allowance(%Plausible.Billing.Subscription{paddle_plan_id: "free_10k"}), do: 10_000 def allowance(subscription) do - found = Enum.find(@all_plans, fn plan -> plan[:product_id] == subscription.paddle_plan_id end) + found = for_product_id(subscription.paddle_plan_id) if found do Map.fetch!(found, :limit) end end + def suggested_plan(user, usage) do + Enum.find(plans_for(user), fn plan -> usage < plan[:limit] end) + end + defp number_format(num) do PlausibleWeb.StatsView.large_number_format(num) end diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex index 42ca64ba3..7d828a5d2 100644 --- a/lib/plausible_web/email.ex +++ b/lib/plausible_web/email.ex @@ -71,6 +71,8 @@ defmodule PlausibleWeb.Email do end def trial_upgrade_email(user, day, {pageviews, custom_events}) do + suggested_plan = Plausible.Billing.Plans.suggested_plan(user, pageviews + custom_events) + base_email() |> to(user) |> tag("trial-upgrade-email") @@ -79,7 +81,8 @@ defmodule PlausibleWeb.Email do user: user, day: day, custom_events: custom_events, - usage: pageviews + custom_events + usage: pageviews + custom_events, + suggested_plan: suggested_plan ) end @@ -112,7 +115,7 @@ defmodule PlausibleWeb.Email do }) end - def over_limit_email(user, usage, last_cycle) do + def over_limit_email(user, usage, last_cycle, suggested_plan) do base_email() |> to(user) |> tag("over-limit") @@ -120,7 +123,8 @@ defmodule PlausibleWeb.Email do |> render("over_limit.html", %{ user: user, usage: usage, - last_cycle: last_cycle + last_cycle: last_cycle, + suggested_plan: suggested_plan }) end diff --git a/lib/plausible_web/templates/billing/_plan_option.html.eex b/lib/plausible_web/templates/billing/_plan_option.html.eex index c624b6be0..d1db3b7f9 100644 --- a/lib/plausible_web/templates/billing/_plan_option.html.eex +++ b/lib/plausible_web/templates/billing/_plan_option.html.eex @@ -1,7 +1,7 @@ <%= @volume %> - <%= @monthly_price %> - <%= @yearly_price %> + <%= @monthly_price %> / mo + <%= @yearly_price %> / yr diff --git a/lib/plausible_web/templates/billing/change_plan.html.eex b/lib/plausible_web/templates/billing/change_plan.html.eex index 5c440717d..254b98fad 100644 --- a/lib/plausible_web/templates/billing/change_plan.html.eex +++ b/lib/plausible_web/templates/billing/change_plan.html.eex @@ -1,5 +1,27 @@
@@ -7,7 +29,7 @@
-
+
Select your new plan
@@ -48,15 +70,9 @@ - <%= render("_plan_option.html", volume: "10k", monthly_price: "$6", yearly_price: "$4") %> - <%= render("_plan_option.html", volume: "100k", monthly_price: "$12", yearly_price: "$8") %> - <%= render("_plan_option.html", volume: "200k", monthly_price: "$18", yearly_price: "$12") %> - <%= render("_plan_option.html", volume: "500k", monthly_price: "$27", yearly_price: "$18") %> - <%= render("_plan_option.html", volume: "1M", monthly_price: "$48", yearly_price: "$32") %> - <%= render("_plan_option.html", volume: "2M", monthly_price: "$69", yearly_price: "$46") %> - <%= render("_plan_option.html", volume: "5M", monthly_price: "$99", yearly_price: "$69") %> - <%= render("_plan_option.html", volume: "10M", monthly_price: "$150", yearly_price: "$100") %> - <%= render("_plan_option.html", volume: "20M", monthly_price: "$225", yearly_price: "$150") %> + <%= for plan <- Plausible.Billing.Plans.plans_for(@conn.assigns[:current_user]) do %> + <%= render("_plan_option.html", volume: PlausibleWeb.StatsView.large_number_format(plan[:limit]), monthly_price: plan[:monthly_cost], yearly_price: plan[:yearly_cost]) %> + <% end %>
@@ -65,12 +81,12 @@
- - diff --git a/lib/plausible_web/templates/billing/upgrade.html.eex b/lib/plausible_web/templates/billing/upgrade.html.eex index 9e4e0a187..d57a8db80 100644 --- a/lib/plausible_web/templates/billing/upgrade.html.eex +++ b/lib/plausible_web/templates/billing/upgrade.html.eex @@ -1,5 +1,27 @@
@@ -8,7 +30,7 @@
-
+
You've used <%= PlausibleWeb.AuthView.delimit_integer(@usage) %> billable pageviews in the last 30 days
@@ -39,20 +61,14 @@ Monthly pageviews - Price per month + Price - <%= render("_plan_option.html", volume: "10k", monthly_price: "$6", yearly_price: "$4") %> - <%= render("_plan_option.html", volume: "100k", monthly_price: "$12", yearly_price: "$8") %> - <%= render("_plan_option.html", volume: "200k", monthly_price: "$18", yearly_price: "$12") %> - <%= render("_plan_option.html", volume: "500k", monthly_price: "$27", yearly_price: "$18") %> - <%= render("_plan_option.html", volume: "1M", monthly_price: "$48", yearly_price: "$32") %> - <%= render("_plan_option.html", volume: "2M", monthly_price: "$69", yearly_price: "$46") %> - <%= render("_plan_option.html", volume: "5M", monthly_price: "$99", yearly_price: "$69") %> - <%= render("_plan_option.html", volume: "10M", monthly_price: "$150", yearly_price: "$100") %> - <%= render("_plan_option.html", volume: "20M", monthly_price: "$225", yearly_price: "$150") %> + <%= for plan <- Plausible.Billing.Plans.plans_for(@user) do %> + <%= render("_plan_option.html", volume: PlausibleWeb.StatsView.large_number_format(plan[:limit]), monthly_price: plan[:monthly_cost], yearly_price: plan[:yearly_cost]) %> + <% end %>
@@ -60,9 +76,9 @@
-
Due today: $6
+
Due today: $6
- diff --git a/lib/plausible_web/templates/email/over_limit.html.eex b/lib/plausible_web/templates/email/over_limit.html.eex index 6fc3ee50f..e142a82ac 100644 --- a/lib/plausible_web/templates/email/over_limit.html.eex +++ b/lib/plausible_web/templates/email/over_limit.html.eex @@ -8,7 +8,7 @@ We don't enforce any hard limits at the moment, we're still counting your stats

In the last billing cycle (<%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %>), your account has used <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews. <%= if @usage <= 20_000_000 do %> -Based on that we recommend you select the <%= Plausible.Billing.Plans.suggested_plan_name(@usage) %> plan which runs at <%= Plausible.Billing.Plans.suggested_plan_cost(@usage) %> or <%= Plausible.Billing.Plans.suggested_plan_cost_yearly(@usage) %> when billed yearly. +Based on that we recommend you select the <%= @suggested_plan[:volume] %>/mo plan which runs at <%= @suggested_plan[:monthly_cost] %>/mo or <%= @suggested_plan[:yearly_cost] %>/yr when billed yearly.

You can upgrade your subscription using our self-serve platform. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.

diff --git a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex index 53e33f0ab..467b5b517 100644 --- a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex +++ b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex @@ -4,7 +4,7 @@ Thanks for exploring Plausible, a simple and privacy-friendly alternative to Goo

In the last month, your account has used <%= PlausibleWeb.AuthView.delimit_integer(@usage) %> billable pageviews<%= if @custom_events > 0, do: " and custom events in total", else: "" %>. <%= if @usage <= 20_000_000 do %> -Based on that we recommend you select the <%= Plausible.Billing.Plans.suggested_plan_name(@usage) %> plan which runs at <%= Plausible.Billing.Plans.suggested_plan_cost(@usage) %>. +Based on that we recommend you select the <%= @suggested_plan[:volume] %>/mo plan which runs at <%= @suggested_plan[:monthly_cost] %>/mo. You can also go with yearly billing to get 33% off on your plan.

diff --git a/lib/plausible_web/views/auth_view.ex b/lib/plausible_web/views/auth_view.ex index 614484e53..0298381ab 100644 --- a/lib/plausible_web/views/auth_view.ex +++ b/lib/plausible_web/views/auth_view.ex @@ -15,7 +15,7 @@ defmodule PlausibleWeb.AuthView do end def subscription_quota(subscription) do - Plans.subscription_quota(subscription.paddle_plan_id) + Plans.allowance(subscription) |> PlausibleWeb.StatsView.large_number_format() end def subscription_interval(subscription) do diff --git a/lib/workers/check_usage.ex b/lib/workers/check_usage.ex index e37009be7..22fb5c6de 100644 --- a/lib/workers/check_usage.ex +++ b/lib/workers/check_usage.ex @@ -53,7 +53,9 @@ defmodule Plausible.Workers.CheckUsage do if last_last_month > allowance && last_month > allowance do {_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber) - template = PlausibleWeb.Email.over_limit_email(subscriber, last_month, last_cycle) + suggested_plan = Plausible.Billing.Plans.suggested_plan(subscriber, last_month) + template = + PlausibleWeb.Email.over_limit_email(subscriber, last_month, last_cycle, suggested_plan) try do Plausible.Mailer.send_email(template) diff --git a/lib/workers/notify_annual_renewal.ex b/lib/workers/notify_annual_renewal.ex index 12f5f3e58..7c89e23a1 100644 --- a/lib/workers/notify_annual_renewal.ex +++ b/lib/workers/notify_annual_renewal.ex @@ -2,7 +2,7 @@ defmodule Plausible.Workers.NotifyAnnualRenewal do use Plausible.Repo use Oban.Worker, queue: :notify_annual_renewal - @yearly_plans Plausible.Billing.Plans.yearly_plan_ids() + @yearly_plans Plausible.Billing.Plans.all_yearly_plan_ids() @impl Oban.Worker @doc """ diff --git a/priv/plans_v1.json b/priv/plans_v1.json new file mode 100644 index 000000000..e69de29bb diff --git a/test/plausible/billing/plans_test.exs b/test/plausible/billing/plans_test.exs index 23ea26dd5..c744f31bb 100644 --- a/test/plausible/billing/plans_test.exs +++ b/test/plausible/billing/plans_test.exs @@ -2,12 +2,4 @@ defmodule Plausible.Billing.PlansTest do use Plausible.DataCase use Bamboo.Test, shared: true alias Plausible.Billing.Plans - - test "suggested plan name" do - assert Plans.suggested_plan_name(110_000) == "200k/mo" - end - - test "suggested plan cost" do - assert Plans.suggested_plan_cost(110_000) == "$18/mo" - end end diff --git a/test/plausible_web/controllers/auth_controller_test.exs b/test/plausible_web/controllers/auth_controller_test.exs index f53271027..878f3f53d 100644 --- a/test/plausible_web/controllers/auth_controller_test.exs +++ b/test/plausible_web/controllers/auth_controller_test.exs @@ -270,6 +270,20 @@ defmodule PlausibleWeb.AuthControllerTest do assert html_response(conn, 200) =~ "10k pageviews" assert html_response(conn, 200) =~ "monthly billing" end + + test "shows yearly subscription", %{conn: conn, user: user} do + insert(:subscription, paddle_plan_id: "590752", user: user) + conn = get(conn, "/settings") + assert html_response(conn, 200) =~ "100k pageviews" + assert html_response(conn, 200) =~ "yearly billing" + end + + test "shows free subscription", %{conn: conn, user: user} do + insert(:subscription, paddle_plan_id: "free_10k", user: user) + conn = get(conn, "/settings") + assert html_response(conn, 200) =~ "10k pageviews" + assert html_response(conn, 200) =~ "N/A billing" + end end describe "PUT /settings" do diff --git a/test/workers/check_usage_test.exs b/test/workers/check_usage_test.exs index 48f512492..5adc80230 100644 --- a/test/workers/check_usage_test.exs +++ b/test/workers/check_usage_test.exs @@ -7,7 +7,7 @@ defmodule Plausible.Workers.CheckUsageTest do alias Plausible.Billing.Plans setup [:create_user, :create_site] - @paddle_id_10k Plans.plans()[:monthly][:"10k"][:product_id] + @paddle_id_10k Plans.plans_for(nil) |> List.first() |> Map.get(:monthly_product_id) test "ignores user without subscription" do CheckUsage.perform(nil) @@ -89,7 +89,8 @@ defmodule Plausible.Workers.CheckUsageTest do assert_email_delivered_with( to: [user], - html_body: ~r/select the 100k\/mo plan which runs at \$12\/mo or \$8\/mo when billed yearly/ + html_body: + ~r/select the 100k\/mo plan which runs at \$12\/mo or \$96\/yr when billed yearly/ ) end diff --git a/test/workers/notify_annual_renewal_test.exs b/test/workers/notify_annual_renewal_test.exs index 5f4b74846..ebd29e084 100644 --- a/test/workers/notify_annual_renewal_test.exs +++ b/test/workers/notify_annual_renewal_test.exs @@ -6,8 +6,9 @@ defmodule Plausible.Workers.NotifyAnnualRenewalTest do alias Plausible.Billing.Plans setup [:create_user, :create_site] - @monthly_plan Plans.plans()[:monthly][:"10k"][:product_id] - @yearly_plan Plans.plans()[:yearly][:"10k"][:product_id] + @plan_10k Plans.plans_for(nil) |> List.first() + @monthly_plan @plan_10k[:monthly_product_id] + @yearly_plan @plan_10k[:yearly_product_id] test "ignores user without subscription" do NotifyAnnualRenewal.perform(nil)