From 885b1d271a92b275e06da4d0f9b68b6f33fbf4d7 Mon Sep 17 00:00:00 2001
From: Uku Taht <uku.taht@gmail.com>
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                | 167 +++++++++---------
 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, 184 insertions(+), 129 deletions(-)
 create mode 100644 priv/plans_v1.json

diff --git a/assets/css/app.css b/assets/css/app.css
index 59cabe88..b6800367 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 e10b09d8..619c2a9a 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 [
     %{
-      product_id: "642354",
-      cost: "$1200",
-      monthly_cost: "$100",
-      limit: 10_000_000,
-      cycle: "yearly"
+      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"
     },
     %{
-      product_id: "642356",
-      cost: "$1800",
+      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"
+    },
+    %{
+      limit: 10_000_000,
+      monthly_product_id: "642352",
       monthly_cost: "$150",
-      limit: 20_000_000,
-      cycle: "yearly"
+      yearly_product_id: "642354",
+      yearly_cost: "$1200"
     },
     %{
-      product_id: "650653",
-      cost: "$2640",
-      monthly_cost: "$220",
-      limit: 50_000_000,
-      cycle: "yearly"
+      limit: 20_000_000,
+      monthly_product_id: "642355",
+      monthly_cost: "$225",
+      yearly_product_id: "642356",
+      yearly_cost: "$1800"
     },
     %{
-      product_id: "648089",
-      cost: "$4800",
-      monthly_cost: "$400",
-      limit: 150_000_000,
-      cycle: "yearly"
+      limit: 50_000_000,
+      monthly_product_id: "650652",
+      monthly_cost: "$330",
+      yearly_product_id: "650653",
+      yearly_cost: "$2640"
     }
   ]
 
-  @all_plans @monthly_plans ++ @yearly_plans
-
-  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(%{})
+  @yearly_plans_v1 [
+    %{limit: 150_000_000, yearly_product_id: "648089", yearly_cost: "$4800"}
+  ]
 
-    %{
-      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 42ca64ba..7d828a5d 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 c624b6be..d1db3b7f 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 @@
 <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 %>'}">
-    <span x-show="billingCycle === 'monthly'"><%= @monthly_price %></span>
-    <span x-show="billingCycle === 'yearly'"><%= @yearly_price %></span>
+    <span x-show="billingCycle === 'monthly'"><%= @monthly_price %> / mo</span>
+    <span x-show="billingCycle === 'yearly'"><%= @yearly_price %> / yr</span>
   </td>
 </tr>
diff --git a/lib/plausible_web/templates/billing/change_plan.html.eex b/lib/plausible_web/templates/billing/change_plan.html.eex
index 5c440717..254b98fa 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 @@
 <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>
 
 <div class="mx-auto mt-6 text-center">
@@ -7,7 +29,7 @@
 </div>
 
 <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">
       Select your new plan
     </div>
@@ -48,15 +70,9 @@
               </tr>
             </thead>
             <tbody class="bg-white dark:bg-gray-800">
-              <%= 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 %>
             </tbody>
           </table>
         </div>
@@ -65,12 +81,12 @@
 
     <div class="mt-6 text-right">
       <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>
           Preview changes
         </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>
           Preview changes
         </a>
diff --git a/lib/plausible_web/templates/billing/upgrade.html.eex b/lib/plausible_web/templates/billing/upgrade.html.eex
index 9e4e0a18..d57a8db8 100644
--- a/lib/plausible_web/templates/billing/upgrade.html.eex
+++ b/lib/plausible_web/templates/billing/upgrade.html.eex
@@ -1,5 +1,27 @@
 <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>
 
 <div class="mx-auto mt-6 text-center">
@@ -8,7 +30,7 @@
 
 <div>
   <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">
         <span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> billable pageviews in the last 30 days</span>
       </div>
@@ -39,20 +61,14 @@
                     Monthly pageviews
                   </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">
-                    Price per month
+                    Price
                   </th>
                 </tr>
               </thead>
               <tbody class="bg-white dark:bg-gray-800">
-              <%= 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 %>
               </tbody>
             </table>
           </div>
@@ -60,9 +76,9 @@
       </div>
 
       <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">
-          <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>
             Pay securely via Paddle
           </button>
diff --git a/lib/plausible_web/templates/email/over_limit.html.eex b/lib/plausible_web/templates/email/over_limit.html.eex
index 6fc3ee50..e142a82a 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
 <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.
 <%= 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 />
 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 />
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 53e33f0a..467b5b51 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
 <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: "" %>.
 <%= 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.
 <br /><br />
diff --git a/lib/plausible_web/views/auth_view.ex b/lib/plausible_web/views/auth_view.ex
index 614484e5..0298381a 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 e37009be..22fb5c6d 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 12f5f3e5..7c89e23a 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 00000000..e69de29b
diff --git a/test/plausible/billing/plans_test.exs b/test/plausible/billing/plans_test.exs
index 23ea26dd..c744f31b 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 f5327102..878f3f53 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 48f51249..5adc8023 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 5f4b7484..ebd29e08 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)
-- 
GitLab