Refactor current plan data format to make changing it easier

This commit is contained in:
Uku Taht 2021-05-06 11:46:22 +03:00
parent 09df65d5ec
commit 885b1d271a
16 changed files with 185 additions and 130 deletions

View File

@ -7,6 +7,8 @@
@import "tooltip.css"; @import "tooltip.css";
@import "flatpickr.css"; @import "flatpickr.css";
[x-cloak] { display: none; }
.button { .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; @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;
} }

View File

@ -1,80 +1,93 @@
defmodule Plausible.Billing.Plans do defmodule Plausible.Billing.Plans do
@monthly_plans [ @plans_v1 [
%{product_id: "558018", cost: "$6", limit: 10_000, cycle: "monthly"}, %{
%{product_id: "558745", cost: "$12", limit: 100_000, cycle: "monthly"}, limit: 10_000,
%{product_id: "597485", cost: "$18", limit: 200_000, cycle: "monthly"}, monthly_product_id: "558018",
%{product_id: "597487", cost: "$27", limit: 500_000, cycle: "monthly"}, monthly_cost: "$6",
%{product_id: "597642", cost: "$48", limit: 1_000_000, cycle: "monthly"}, yearly_product_id: "572810",
%{product_id: "597309", cost: "$69", limit: 2_000_000, cycle: "monthly"}, yearly_cost: "$48"
%{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"}, limit: 100_000,
%{product_id: "650652", cost: "$330", limit: 50_000_000, cycle: "monthly"} monthly_product_id: "558745",
] monthly_cost: "$12",
yearly_product_id: "590752",
@yearly_plans [ yearly_cost: "$96"
%{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"}, limit: 200_000,
%{product_id: "597488", cost: "$216", monthly_cost: "$18", limit: 500_000, cycle: "yearly"}, monthly_product_id: "597485",
%{product_id: "597643", cost: "$384", monthly_cost: "$32", limit: 1_000_000, cycle: "yearly"}, monthly_cost: "$18",
%{product_id: "597310", cost: "$552", monthly_cost: "$46", limit: 2_000_000, cycle: "yearly"}, yearly_product_id: "597486",
%{product_id: "597312", cost: "$792", monthly_cost: "$66", limit: 5_000_000, cycle: "yearly"}, 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, limit: 10_000_000,
cycle: "yearly" monthly_product_id: "642352",
},
%{
product_id: "642356",
cost: "$1800",
monthly_cost: "$150", monthly_cost: "$150",
yearly_product_id: "642354",
yearly_cost: "$1200"
},
%{
limit: 20_000_000, 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, limit: 50_000_000,
cycle: "yearly" monthly_product_id: "650652",
}, monthly_cost: "$330",
%{ yearly_product_id: "650653",
product_id: "648089", yearly_cost: "$2640"
cost: "$4800",
monthly_cost: "$400",
limit: 150_000_000,
cycle: "yearly"
} }
] ]
@all_plans @monthly_plans ++ @yearly_plans @yearly_plans_v1 [
%{limit: 150_000_000, yearly_product_id: "648089", yearly_cost: "$4800"}
]
def plans do def plans_for(user) do
monthly = @plans_v1 |> Enum.map(fn plan -> Map.put(plan, :volume, number_format(plan[:limit])) end)
@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
}
end end
def yearly_plan_ids do def all_yearly_plan_ids do
Enum.map(@yearly_plans, fn plan -> plan[:product_id] end) Enum.map(@plans_v1, fn plan -> plan[:yearly_product_id] end)
end end
def for_product_id(product_id) do 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 end
def subscription_quota("free_10k"), do: "10k" def subscription_quota("free_10k"), do: "10k"
@ -90,38 +103,32 @@ defmodule Plausible.Billing.Plans do
def subscription_interval(product_id) do def subscription_interval(product_id) do
case for_product_id(product_id) do case for_product_id(product_id) do
nil -> raise "Unknown interval for subscription #{product_id}" nil ->
product -> product[:cycle] raise "Unknown interval for subscription #{product_id}"
plan ->
if product_id == plan[:monthly_product_id] do
"monthly"
else
"yearly"
end
end end
end end
def suggested_plan_name(usage) do def allowance(%Plausible.Billing.Subscription{paddle_plan_id: "free_10k"}), do: 10_000
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(subscription) do 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 if found do
Map.fetch!(found, :limit) Map.fetch!(found, :limit)
end end
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 defp number_format(num) do
PlausibleWeb.StatsView.large_number_format(num) PlausibleWeb.StatsView.large_number_format(num)
end end

View File

@ -71,6 +71,8 @@ defmodule PlausibleWeb.Email do
end end
def trial_upgrade_email(user, day, {pageviews, custom_events}) do def trial_upgrade_email(user, day, {pageviews, custom_events}) do
suggested_plan = Plausible.Billing.Plans.suggested_plan(user, pageviews + custom_events)
base_email() base_email()
|> to(user) |> to(user)
|> tag("trial-upgrade-email") |> tag("trial-upgrade-email")
@ -79,7 +81,8 @@ defmodule PlausibleWeb.Email do
user: user, user: user,
day: day, day: day,
custom_events: custom_events, custom_events: custom_events,
usage: pageviews + custom_events usage: pageviews + custom_events,
suggested_plan: suggested_plan
) )
end end
@ -112,7 +115,7 @@ defmodule PlausibleWeb.Email do
}) })
end end
def over_limit_email(user, usage, last_cycle) do def over_limit_email(user, usage, last_cycle, suggested_plan) do
base_email() base_email()
|> to(user) |> to(user)
|> tag("over-limit") |> tag("over-limit")
@ -120,7 +123,8 @@ defmodule PlausibleWeb.Email do
|> render("over_limit.html", %{ |> render("over_limit.html", %{
user: user, user: user,
usage: usage, usage: usage,
last_cycle: last_cycle last_cycle: last_cycle,
suggested_plan: suggested_plan
}) })
end end

View File

@ -1,7 +1,7 @@
<tr @click="volume = '<%= @volume %>'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '<%= @volume %>', 'border-b border-gray-200 cursor-pointer': volume !== '<%= @volume %>'}"> <tr @click="volume = '<%= @volume %>'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '<%= @volume %>', 'border-b border-gray-200 cursor-pointer': volume !== '<%= @volume %>'}">
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '<%= @volume %>'}"><%= @volume %></td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '<%= @volume %>'}"><%= @volume %></td>
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '<%= @volume %>'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '<%= @volume %>'}">
<span x-show="billingCycle === 'monthly'"><%= @monthly_price %></span> <span x-show="billingCycle === 'monthly'"><%= @monthly_price %> / mo</span>
<span x-show="billingCycle === 'yearly'"><%= @yearly_price %></span> <span x-show="billingCycle === 'yearly'"><%= @yearly_price %> / yr</span>
</td> </td>
</tr> </tr>

View File

@ -1,5 +1,27 @@
<script> <script>
window.plans = <%= raw Jason.encode!(Plausible.Billing.Plans.plans()) %> window.plans = function() {
return {
rawPlans: <%= raw Jason.encode!(Plausible.Billing.Plans.plans_for(@conn.assigns[:current_user])) %>,
volume: '10k',
billingCycle: 'monthly',
selectedPlanPrice() {
var selectedPlan = this.rawPlans.find((plan) => plan.volume === this.volume)
if (this.billingCycle === 'monthly'){
return selectedPlan.monthly_cost
} else {
return selectedPlan.yearly_cost
}
},
selectedPlanProductId() {
var selectedPlan = this.rawPlans.find((plan) => plan.volume === this.volume)
if (this.billingCycle === 'monthly'){
return selectedPlan.monthly_product_id
} else {
return selectedPlan.yearly_product_id
}
}
}
}
</script> </script>
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
@ -7,7 +29,7 @@
</div> </div>
<div class="w-full max-w-lg px-4 mx-auto mt-4"> <div class="w-full max-w-lg px-4 mx-auto mt-4">
<div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 px-8 py-4 mt-8 mb-4 bg-white rounded shadow-md dark:bg-gray-800"> <div x-data="window.plans()" class="flex-1 px-8 py-4 mt-8 mb-4 bg-white rounded shadow-md dark:bg-gray-800">
<div class="w-full pt-2 text-xl font-bold dark:text-gray-100"> <div class="w-full pt-2 text-xl font-bold dark:text-gray-100">
Select your new plan Select your new plan
</div> </div>
@ -48,15 +70,9 @@
</tr> </tr>
</thead> </thead>
<tbody class="bg-white dark:bg-gray-800"> <tbody class="bg-white dark:bg-gray-800">
<%= render("_plan_option.html", volume: "10k", monthly_price: "$6", yearly_price: "$4") %> <%= for plan <- Plausible.Billing.Plans.plans_for(@conn.assigns[:current_user]) do %>
<%= render("_plan_option.html", volume: "100k", monthly_price: "$12", yearly_price: "$8") %> <%= render("_plan_option.html", volume: PlausibleWeb.StatsView.large_number_format(plan[:limit]), monthly_price: plan[:monthly_cost], yearly_price: plan[:yearly_cost]) %>
<%= render("_plan_option.html", volume: "200k", monthly_price: "$18", yearly_price: "$12") %> <% end %>
<%= 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") %>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -65,12 +81,12 @@
<div class="mt-6 text-right"> <div class="mt-6 text-right">
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<a x-show="window.plans[billingCycle][volume].product_id !== '<%= @subscription.paddle_plan_id %>'" style="display: none;" :href="'/billing/change-plan/preview/' + window.plans[billingCycle][volume].product_id" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent leading-5 rounded-md hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150"> <a x-show="selectedPlanProductId() !== '<%= @subscription.paddle_plan_id %>'" style="display: none;" :href="'/billing/change-plan/preview/' + selectedPlanProductId()" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent leading-5 rounded-md hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150">
<svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg> <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg>
Preview changes Preview changes
</a> </a>
<a x-show="window.plans[billingCycle][volume].product_id === '<%= @subscription.paddle_plan_id %>'" style="display: none;" tooltip="Select a different plan to continue" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-gray-400 border border-transparent leading-5 rounded-md"> <a x-show="selectedPlanProductId() === '<%= @subscription.paddle_plan_id %>'" style="display: none;" tooltip="Select a different plan to continue" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-gray-400 border border-transparent leading-5 rounded-md">
<svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg> <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg>
Preview changes Preview changes
</a> </a>

View File

@ -1,5 +1,27 @@
<script> <script>
window.plans = <%= raw Jason.encode!(Plausible.Billing.Plans.plans()) %> window.plans = function() {
return {
rawPlans: <%= raw Jason.encode!(Plausible.Billing.Plans.plans_for(@user)) %>,
volume: '10k',
billingCycle: 'monthly',
selectedPlanPrice() {
var selectedPlan = this.rawPlans.find((plan) => plan.volume === this.volume)
if (this.billingCycle === 'monthly'){
return selectedPlan.monthly_cost
} else {
return selectedPlan.yearly_cost
}
},
selectedPlanProductId() {
var selectedPlan = this.rawPlans.find((plan) => plan.volume === this.volume)
if (this.billingCycle === 'monthly'){
return selectedPlan.monthly_product_id
} else {
return selectedPlan.yearly_product_id
}
}
}
}
</script> </script>
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
@ -8,7 +30,7 @@
<div> <div>
<div class="flex flex-col w-full max-w-4xl px-4 mx-auto mt-4 md:flex-row"> <div class="flex flex-col w-full max-w-4xl px-4 mx-auto mt-4 md:flex-row">
<div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 px-8 py-4 mt-8 mb-4 bg-white rounded shadow-md dark:bg-gray-800"> <div x-data="window.plans()" class="flex-1 px-8 py-4 mt-8 mb-4 bg-white rounded shadow-md dark:bg-gray-800">
<div class="w-full py-4 dark:text-gray-100"> <div class="w-full py-4 dark:text-gray-100">
<span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> billable pageviews in the last 30 days</span> <span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> billable pageviews in the last 30 days</span>
</div> </div>
@ -39,20 +61,14 @@
Monthly pageviews Monthly pageviews
</th> </th>
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase bg-gray-100 border-b border-gray-200 dark:bg-gray-900 leading-4 dark:text-gray-200"> <th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase bg-gray-100 border-b border-gray-200 dark:bg-gray-900 leading-4 dark:text-gray-200">
Price per month Price
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white dark:bg-gray-800"> <tbody class="bg-white dark:bg-gray-800">
<%= render("_plan_option.html", volume: "10k", monthly_price: "$6", yearly_price: "$4") %> <%= for plan <- Plausible.Billing.Plans.plans_for(@user) do %>
<%= render("_plan_option.html", volume: "100k", monthly_price: "$12", yearly_price: "$8") %> <%= render("_plan_option.html", volume: PlausibleWeb.StatsView.large_number_format(plan[:limit]), monthly_price: plan[:monthly_cost], yearly_price: plan[:yearly_cost]) %>
<%= render("_plan_option.html", volume: "200k", monthly_price: "$18", yearly_price: "$12") %> <% end %>
<%= 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") %>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -60,9 +76,9 @@
</div> </div>
<div class="mt-6 text-right"> <div class="mt-6 text-right">
<div class="mb-4 text-sm font-medium dark:text-gray-100">Due today: <b x-text="window.plans[billingCycle][volume].cost">$6</b></div> <div class="mb-4 text-sm font-medium dark:text-gray-100">Due today: <b x-text="selectedPlanPrice()">$6</b></div>
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<button type="button" data-theme="none" :data-product="window.plans[billingCycle][volume].product_id" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent paddle_button leading-5 rounded-md hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150"> <button type="button" data-theme="none" :data-product="selectedPlanProductId()" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent paddle_button leading-5 rounded-md hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150">
<svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg> <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
Pay securely via Paddle Pay securely via Paddle
</button> </button>

View File

@ -8,7 +8,7 @@ We don't enforce any hard limits at the moment, we're still counting your stats
<br /><br /> <br /><br />
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. 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 %> <%= 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.
<br /><br /> <br /><br />
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. 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.
<br /><br /> <br /><br />

View File

@ -4,7 +4,7 @@ Thanks for exploring Plausible, a simple and privacy-friendly alternative to Goo
<br /><br /> <br /><br />
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: "" %>. 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 %> <%= 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. You can also go with yearly billing to get 33% off on your plan.
<br /><br /> <br /><br />

View File

@ -15,7 +15,7 @@ defmodule PlausibleWeb.AuthView do
end end
def subscription_quota(subscription) do def subscription_quota(subscription) do
Plans.subscription_quota(subscription.paddle_plan_id) Plans.allowance(subscription) |> PlausibleWeb.StatsView.large_number_format()
end end
def subscription_interval(subscription) do def subscription_interval(subscription) do

View File

@ -53,7 +53,9 @@ defmodule Plausible.Workers.CheckUsage do
if last_last_month > allowance && last_month > allowance do if last_last_month > allowance && last_month > allowance do
{_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber) {_, 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 try do
Plausible.Mailer.send_email(template) Plausible.Mailer.send_email(template)

View File

@ -2,7 +2,7 @@ defmodule Plausible.Workers.NotifyAnnualRenewal do
use Plausible.Repo use Plausible.Repo
use Oban.Worker, queue: :notify_annual_renewal 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 @impl Oban.Worker
@doc """ @doc """

0
priv/plans_v1.json Normal file
View File

View File

@ -2,12 +2,4 @@ defmodule Plausible.Billing.PlansTest do
use Plausible.DataCase use Plausible.DataCase
use Bamboo.Test, shared: true use Bamboo.Test, shared: true
alias Plausible.Billing.Plans 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 end

View File

@ -270,6 +270,20 @@ defmodule PlausibleWeb.AuthControllerTest do
assert html_response(conn, 200) =~ "10k pageviews" assert html_response(conn, 200) =~ "10k pageviews"
assert html_response(conn, 200) =~ "monthly billing" assert html_response(conn, 200) =~ "monthly billing"
end 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 end
describe "PUT /settings" do describe "PUT /settings" do

View File

@ -7,7 +7,7 @@ defmodule Plausible.Workers.CheckUsageTest do
alias Plausible.Billing.Plans alias Plausible.Billing.Plans
setup [:create_user, :create_site] 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 test "ignores user without subscription" do
CheckUsage.perform(nil) CheckUsage.perform(nil)
@ -89,7 +89,8 @@ defmodule Plausible.Workers.CheckUsageTest do
assert_email_delivered_with( assert_email_delivered_with(
to: [user], 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 end

View File

@ -6,8 +6,9 @@ defmodule Plausible.Workers.NotifyAnnualRenewalTest do
alias Plausible.Billing.Plans alias Plausible.Billing.Plans
setup [:create_user, :create_site] setup [:create_user, :create_site]
@monthly_plan Plans.plans()[:monthly][:"10k"][:product_id] @plan_10k Plans.plans_for(nil) |> List.first()
@yearly_plan Plans.plans()[:yearly][:"10k"][:product_id] @monthly_plan @plan_10k[:monthly_product_id]
@yearly_plan @plan_10k[:yearly_product_id]
test "ignores user without subscription" do test "ignores user without subscription" do
NotifyAnnualRenewal.perform(nil) NotifyAnnualRenewal.perform(nil)