Add enterprise plans

This commit is contained in:
Uku Taht 2021-10-20 16:49:11 +02:00
parent fa1e39133d
commit 6a5b383e2b
21 changed files with 519 additions and 72 deletions

View File

@ -377,6 +377,14 @@ config :kaffy,
resources: [
site: [schema: Plausible.Site, admin: Plausible.SiteAdmin]
]
],
billing: [
resources: [
enterprise_plan: [
schema: Plausible.Billing.EnterprisePlan,
admin: Plausible.Billing.EnterprisePlanAdmin
]
]
]
]

View File

@ -25,6 +25,7 @@ defmodule Plausible.Auth.User do
has_many :api_keys, Plausible.Auth.ApiKey
has_one :google_auth, Plausible.Site.GoogleAuth
has_one :subscription, Plausible.Billing.Subscription
has_one :enterprise_plan, Plausible.Billing.EnterprisePlan
timestamps()
end

View File

@ -0,0 +1,30 @@
defmodule Plausible.Billing.EnterprisePlan do
use Ecto.Schema
import Ecto.Changeset
@required_fields [
:user_id,
:paddle_plan_id,
:billing_interval,
:monthly_pageview_limit,
:hourly_api_request_limit
]
schema "enterprise_plans" do
field :paddle_plan_id, :string
field :billing_interval, Ecto.Enum, values: [:monthly, :yearly]
field :monthly_pageview_limit, :integer
field :hourly_api_request_limit, :integer
belongs_to :user, Plausible.Auth.User
timestamps()
end
def changeset(model, attrs \\ %{}) do
model
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> unique_constraint(:user_id)
end
end

View File

@ -0,0 +1,37 @@
defmodule Plausible.Billing.EnterprisePlanAdmin do
use Plausible.Repo
def search_fields(_schema) do
[
:paddle_plan_id,
user: [:name, :email]
]
end
def form_fields(_) do
[
user_id: nil,
paddle_plan_id: nil,
billing_interval: %{choices: [{"Yearly", "yearly"}, {"Monthly", "monthly"}]},
monthly_pageview_limit: nil,
hourly_api_request_limit: nil
]
end
def custom_index_query(_conn, _schema, query) do
from(r in query, preload: :user)
end
def index(_) do
[
id: nil,
user_email: %{value: &get_user_email/1},
paddle_plan_id: nil,
billing_interval: nil,
monthly_pageview_limit: nil,
hourly_api_request_limit: nil
]
end
defp get_user_email(plan), do: plan.user.email
end

View File

@ -1,4 +1,6 @@
defmodule Plausible.Billing.Plans do
use Plausible.Repo
@unlisted_plans_v1 [
%{limit: 150_000_000, yearly_product_id: "648089", yearly_cost: "$4800"}
]
@ -30,21 +32,15 @@ defmodule Plausible.Billing.Plans do
end)
end
def subscription_quota("free_10k"), do: "10k"
def subscription_quota(product_id) do
case for_product_id(product_id) do
nil -> raise "Unknown quota for subscription #{product_id}"
product -> number_format(product[:limit])
end
end
def subscription_interval("free_10k"), do: "N/A"
def subscription_interval(product_id) do
case for_product_id(product_id) do
nil ->
raise "Unknown interval for subscription #{product_id}"
enterprise_plan =
Repo.get_by(Plausible.Billing.EnterprisePlan, paddle_plan_id: product_id)
enterprise_plan && enterprise_plan.billing_interval
plan ->
if product_id == plan[:monthly_product_id] do
@ -62,6 +58,11 @@ defmodule Plausible.Billing.Plans do
if found do
Map.fetch!(found, :limit)
else
enterprise_plan =
Repo.get_by(Plausible.Billing.EnterprisePlan, paddle_plan_id: subscription.paddle_plan_id)
enterprise_plan && enterprise_plan.monthly_pageview_limit
end
end

View File

@ -14,4 +14,16 @@ defmodule Plausible.Mailer do
reraise error, __STACKTRACE__
end
end
def send_email_safe(email) do
try do
Plausible.Mailer.deliver_now!(email)
rescue
error ->
Sentry.capture_exception(error,
stacktrace: __STACKTRACE__,
extra: %{extra: "Error while sending email"}
)
end
end
end

View File

@ -6,20 +6,122 @@ defmodule PlausibleWeb.BillingController do
plug PlausibleWeb.RequireAccountPlug
def admin_email do
Application.get_env(:plausible, :admin_email)
def upgrade(conn, _params) do
user =
conn.assigns[:current_user]
|> Repo.preload(:enterprise_plan)
cond do
user.subscription && user.subscription.status == "active" ->
redirect(conn, to: Routes.billing_path(conn, :change_plan_form))
user.enterprise_plan ->
redirect(conn,
to: Routes.billing_path(conn, :upgrade_enterprise_plan, user.enterprise_plan.id)
)
true ->
render(conn, "upgrade.html",
usage: Plausible.Billing.usage(user),
user: user,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
end
def upgrade_enterprise_plan(conn, %{"plan_id" => plan_id}) do
user =
conn.assigns[:current_user]
|> Repo.preload(:enterprise_plan)
if user.enterprise_plan && user.enterprise_plan.id == String.to_integer(plan_id) do
usage = Plausible.Billing.usage(conn.assigns[:current_user])
render(conn, "upgrade_to_plan.html",
usage: usage,
user: user,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
else
render_error(conn, 404)
end
end
def upgrade_to_plan(conn, %{"plan_id" => plan_id}) do
plan = Plausible.Billing.Plans.for_product_id(plan_id)
if plan do
cycle = if plan[:monthly_product_id] == plan_id, do: "monthly", else: "yearly"
plan = Map.merge(plan, %{cycle: cycle, product_id: plan_id})
usage = Plausible.Billing.usage(conn.assigns[:current_user])
render(conn, "upgrade_to_plan.html",
usage: usage,
plan: plan,
user: conn.assigns[:current_user],
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
else
render_error(conn, 404)
end
end
def upgrade_success(conn, _params) do
render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
end
def change_plan_form(conn, _params) do
subscription = Billing.active_subscription_for(conn.assigns[:current_user].id)
user =
conn.assigns[:current_user]
|> Repo.preload(:enterprise_plan)
if subscription do
subscription = Billing.active_subscription_for(user.id)
cond do
subscription && user.enterprise_plan ->
redirect(conn,
to: Routes.billing_path(conn, :change_enterprise_plan, user.enterprise_plan.id)
)
subscription ->
render(conn, "change_plan.html",
subscription: subscription,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
else
true ->
redirect(conn, to: Routes.billing_path(conn, :upgrade))
end
end
def change_enterprise_plan(conn, %{"plan_id" => plan_id}) do
user =
conn.assigns[:current_user]
|> Repo.preload(:enterprise_plan)
cond do
is_nil(user.subscription) ->
redirect(conn, to: "/billing/upgrade")
is_nil(user.enterprise_plan) ->
render_error(conn, 404)
user.enterprise_plan.id !== String.to_integer(plan_id) ->
render_error(conn, 404)
user.enterprise_plan.paddle_plan_id == user.subscription.paddle_plan_id ->
render(conn, "change_enterprise_plan_contact_us.html",
user: user,
plan: user.enterprise_plan,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
true ->
render(conn, "change_enterprise_plan.html",
user: user,
plan: user.enterprise_plan,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
end
@ -77,39 +179,4 @@ defmodule PlausibleWeb.BillingController do
|> redirect(to: "/settings")
end
end
def upgrade(conn, _params) do
usage = Plausible.Billing.usage(conn.assigns[:current_user])
today = Timex.today()
render(conn, "upgrade.html",
usage: usage,
today: today,
user: conn.assigns[:current_user],
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
def upgrade_to_plan(conn, %{"plan_id" => plan_id}) do
plan = Plausible.Billing.Plans.for_product_id(plan_id)
if plan do
cycle = if plan[:monthly_product_id] == plan_id, do: "monthly", else: "yearly"
plan = Map.merge(plan, %{cycle: cycle, product_id: plan_id})
usage = Plausible.Billing.usage(conn.assigns[:current_user])
render(conn, "upgrade_to_plan.html",
usage: usage,
plan: plan,
user: conn.assigns[:current_user],
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
else
render_error(conn, 404)
end
end
def upgrade_success(conn, _params) do
render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
end
end

View File

@ -128,6 +128,18 @@ defmodule PlausibleWeb.Email do
})
end
def enterprise_over_limit_email(user, usage, last_cycle) do
base_email()
|> to("enterprise@plausible.io")
|> tag("enterprise-over-limit")
|> subject("#{user.email} has outgrown their enterprise plan")
|> render("enterprise_over_limit.html", %{
user: user,
usage: usage,
last_cycle: last_cycle
})
end
def yearly_renewal_notification(user) do
date = Timex.format!(user.subscription.next_bill_date, "{Mfull} {D}, {YYYY}")

View File

@ -147,6 +147,8 @@ defmodule PlausibleWeb.Router do
post "/billing/change-plan/:new_plan_id", BillingController, :change_plan
get "/billing/upgrade", BillingController, :upgrade
get "/billing/upgrade/:plan_id", BillingController, :upgrade_to_plan
get "/billing/upgrade/enterprise/:plan_id", BillingController, :upgrade_enterprise_plan
get "/billing/change-plan/enterprise/:plan_id", BillingController, :change_enterprise_plan
get "/billing/upgrade-success", BillingController, :upgrade_success
get "/sites", SiteController, :index

View File

@ -0,0 +1,68 @@
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.1.3/fetch-jsonp.min.js"></script>
<div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black dark:text-gray-100">Change subscription plan</h1>
</div>
<script>
plan = function() {
return {
localizedPlan: null,
price() {
var currency = {
'USD': '$',
'EUR': '',
'GBP': '£'
}[this.localizedPlan.currency]
return currency + this.localizedPlan.price.net
},
fetchPlan() {
fetchJsonp('https://checkout.paddle.com/api/2.0/prices?product_ids=<%= @plan.paddle_plan_id %>')
.then(res => res.json())
.then((data) => {
this.localizedPlan = data.response.products[0]
})
}
}
}
</script>
<div class="w-full max-w-lg px-4 mx-auto mt-4">
<div x-init="fetchPlan()" x-data="window.plan()" class="flex-1 p-8 mt-8 bg-white rounded shadow-md dark:bg-gray-800">
<div x-show="!localizedPlan" class="mx-auto my-40 loading sm"><div></div></div>
<template x-if="localizedPlan">
<div>
<div class="w-full pb-4 dark:text-gray-100">
<span>We've prepared your account for an upgrade to custom limits outside the listed plans:</span>
</div>
<ul class="w-full py-4 dark:text-gray-100">
<li>Up to <b><%= PlausibleWeb.StatsView.large_number_format(@plan.monthly_pageview_limit) %></b> monthly pageviews</li>
<li>Up to <b><%= PlausibleWeb.StatsView.large_number_format(@plan.hourly_api_request_limit) %></b> hourly api requests</li>
</ul>
<ul class="w-full py-4 dark:text-gray-100">
<span>The plan is priced at</span>
<template x-if="localizedPlan"><b x-text="price()"></b> </template>
<span>per <%= if @plan.billing_interval == :yearly, do: "year", else: "month" %>. On the next page, our payment provider will calculate the prorated amount that your card will be charged if you decide to upgrade now.</span>
</ul>
<div class="mt-6 text-left">
<span class="inline-flex w-full rounded-md shadow-sm">
<%= link(to: Routes.billing_path(@conn, :change_plan_preview, @plan.paddle_plan_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 ") do %>
<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
<% end %>
</span>
</div>
</div>
</template>
</div>
</div>
<div class="mt-8 text-center dark:text-gray-100">
Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
</div>
<script type="text/javascript" src="https://cdn.paddle.com/paddle/paddle.js"></script>
<script>Paddle.Setup({vendor: 49430})</script>

View File

@ -0,0 +1,16 @@
<div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black dark:text-gray-100">Change subscription plan</h1>
</div>
<div class="w-full max-w-lg px-4 mx-auto mt-4">
<div class="flex-1 p-8 mt-8 bg-white rounded shadow-md dark:bg-gray-800">
<div class="w-full pb-4 dark:text-gray-100">
<span>Need to change your limits?</span>
</div>
<ul class="w-full py-4 dark:text-gray-100">
<span>Your account is on an enterprise plan. If you want to increase or decrease the limits on your account, please contact us at enterprise@plausible.io</span>
</ul>
</div>
</div>

View File

@ -36,7 +36,7 @@
<div class="pt-6"></div>
<div class="py-2 text-lg font-bold">Next payment</div>
<div class="py-4 dark:text-gray-100 text-lg font-bold">Next payment</div>
<div class="flex flex-col">

View File

@ -25,7 +25,6 @@
'GBP': '£'
}[plan.currency]
console.log(plan)
return currency + plan.price.net
},
fetchPlans() {

View File

@ -0,0 +1,44 @@
<div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black dark:text-gray-100">Upgrade your free trial</h1>
</div>
<div>
<div class="flex flex-col w-full max-w-4xl px-4 mx-auto mt-4 md:flex-row">
<div 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>
<div class="w-full py-4 dark:text-gray-100">
<span>With this link you can upgrade to an enterprise plan with <b><%= PlausibleWeb.StatsView.large_number_format(@plan[:limit]) %> monthly pageviews</b></span>, billed on a <%= @plan[:cycle] %> basis.
</div>
<div class="mt-6 text-left">
<span class="inline-flex w-full rounded-md shadow-sm">
<button type="button" data-theme="none" data-product="<%= @plan[: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="items-center button paddle_button">
<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>
</span>
</div>
</div>
<div class="flex-1 pl-8 pt-14">
<h3 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">
What happens if I go over my page views limit?
</h3>
<div class="mt-2 text-base text-gray-500 leading-6 dark:text-gray-200">
You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly.<br /><br />
If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point.
</div>
</div>
</div>
</div>
<div class="mt-8 text-center dark:text-gray-100">
Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
</div>
<script type="text/javascript" src="https://cdn.paddle.com/paddle/paddle.js"></script>
<script>Paddle.Setup({vendor: 49430})</script>

View File

@ -10,12 +10,12 @@
</div>
<div class="w-full py-4 dark:text-gray-100">
<span>With this link you can upgrade to a plan with <b><%= PlausibleWeb.StatsView.large_number_format(@plan[:limit]) %> monthly pageviews</b></span>, billed on a <%= @plan[:cycle] %> basis.
<span>With this link you can upgrade to an enterprise plan with <b><%= PlausibleWeb.StatsView.large_number_format(@user.enterprise_plan.monthly_pageview_limit) %> monthly pageviews</b></span>, billed on a <%= @user.enterprise_plan.billing_interval %> basis.
</div>
<div class="mt-6 text-left">
<span class="inline-flex w-full rounded-md shadow-sm">
<button type="button" data-theme="none" data-product="<%= @plan[: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="items-center button paddle_button">
<button type="button" data-theme="none" data-product="<%= @user.enterprise_plan.paddle_plan_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="items-center button paddle_button">
<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>

View File

@ -0,0 +1,8 @@
Automated notice about an account that has gone over their enteprise plan limit.
Customer email: <% @user.email %>
Last billing cycle: <%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %>
Usage: <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews
--<br />
<%= plausible_url() %><br />

View File

@ -38,31 +38,47 @@ defmodule Plausible.Workers.CheckUsage do
from u in Plausible.Auth.User,
join: s in Plausible.Billing.Subscription,
on: s.user_id == u.id,
left_join: ep in Plausible.Billing.EnterprisePlan,
on: ep.user_id == u.id,
where: s.status == "active",
where: not is_nil(s.last_bill_date),
# Accounts for situations like last_bill_date==2021-01-31 AND today==2021-03-01. Since February never reaches the 31st day, the account is checked on 2021-03-01.
where:
least(day_of_month(s.last_bill_date), day_of_month(last_day_of_month(^yesterday))) ==
day_of_month(^yesterday),
preload: [subscription: s]
preload: [subscription: s, enterprise_plan: ep]
)
for subscriber <- active_subscribers do
allowance = Plausible.Billing.Plans.allowance(subscriber.subscription)
{last_last_month, last_month} = billing_mod.last_two_billing_months_usage(subscriber)
is_over_limit = last_last_month > allowance && last_month > allowance
if last_last_month > allowance && last_month > allowance do
cond do
is_over_limit && subscriber.enterprise_plan ->
{_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber)
template =
PlausibleWeb.Email.enterprise_over_limit_email(subscriber, last_month, last_cycle)
Plausible.Mailer.send_email_safe(template)
is_over_limit ->
{_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber)
suggested_plan = Plausible.Billing.Plans.suggested_plan(subscriber, last_month)
template =
PlausibleWeb.Email.over_limit_email(subscriber, last_month, last_cycle, suggested_plan)
PlausibleWeb.Email.over_limit_email(
subscriber,
last_month,
last_cycle,
suggested_plan
)
try do
Plausible.Mailer.send_email(template)
rescue
_ -> nil
end
Plausible.Mailer.send_email_safe(template)
true ->
nil
end
end

View File

@ -0,0 +1,19 @@
defmodule Plausible.Repo.Migrations.AddEnterprisePlans do
use Ecto.Migration
def change do
create_query = "CREATE TYPE billing_interval AS ENUM ('monthly', 'yearly')"
drop_query = "DROP TYPE billing_interval"
execute(create_query, drop_query)
create table(:enterprise_plans) do
add :user_id, references(:users), null: false, unique: true
add :paddle_plan_id, :string, null: false
add :billing_interval, :billing_interval, null: false
add :monthly_pageview_limit, :integer, null: false
add :hourly_api_request_limit, :integer, null: false
timestamps()
end
end
end

View File

@ -2,6 +2,46 @@ defmodule PlausibleWeb.BillingControllerTest do
use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /upgrade" do
setup [:create_user, :log_in]
test "shows upgrade page when user does not have a subcription already", %{conn: conn} do
conn = get(conn, "/billing/upgrade")
assert html_response(conn, 200) =~ "Upgrade your free trial"
end
test "redirects user to change plan if they already have a plan", %{conn: conn, user: user} do
insert(:subscription, user: user)
conn = get(conn, "/billing/upgrade")
assert redirected_to(conn) == "/billing/change-plan"
end
test "redirects user to enteprise plan page if they are configured with one", %{
conn: conn,
user: user
} do
plan = insert(:enterprise_plan, user: user)
conn = get(conn, "/billing/upgrade")
assert redirected_to(conn) == "/billing/upgrade/enterprise/#{plan.id}"
end
end
describe "GET /upgrade/enterprise/:plan_id" do
setup [:create_user, :log_in]
test "renders enteprise plan upgrade page", %{conn: conn, user: user} do
plan = insert(:enterprise_plan, user: user)
conn = get(conn, "/billing/upgrade/enterprise/#{plan.id}")
assert html_response(conn, 200) =~ "Upgrade your free trial"
assert html_response(conn, 200) =~ "enterprise plan"
end
end
describe "GET /change-plan" do
setup [:create_user, :log_in]
@ -17,6 +57,37 @@ defmodule PlausibleWeb.BillingControllerTest do
assert redirected_to(conn) == "/billing/upgrade"
end
test "redirects to enterprise change plan page if user has enterprise plan and existing subscription",
%{conn: conn, user: user} do
insert(:subscription, user: user)
plan = insert(:enterprise_plan, user: user)
conn = get(conn, "/billing/change-plan")
assert redirected_to(conn) == "/billing/change-plan/enterprise/#{plan.id}"
end
end
describe "GET /change-plan/enterprise/:plan_id" do
setup [:create_user, :log_in]
test "shows change plan page if user has subsription and enterprise plan", %{
conn: conn,
user: user
} do
insert(:subscription, user: user)
plan = insert(:enterprise_plan, user: user)
conn = get(conn, "/billing/change-plan/enterprise/#{plan.id}")
assert html_response(conn, 200) =~ "Change subscription plan"
end
test "renders 404 is user does not have enterprise plan", %{conn: conn, user: user} do
insert(:subscription, user: user)
conn = get(conn, "/billing/change-plan/enterprise/123")
assert conn.status == 404
end
end
describe "POST /change-plan" do

View File

@ -116,6 +116,15 @@ defmodule Plausible.Factory do
}
end
def enterprise_plan_factory do
%Plausible.Billing.EnterprisePlan{
paddle_plan_id: sequence(:paddle_plan_id, &"plan-#{&1}"),
billing_interval: :monthly,
monthly_pageview_limit: 1_000_000,
hourly_api_request_limit: 3000
}
end
def google_auth_factory do
%Plausible.Site.GoogleAuth{
email: sequence(:google_auth_email, &"email-#{&1}@email.com"),

View File

@ -68,6 +68,33 @@ defmodule Plausible.Workers.CheckUsageTest do
)
end
test "checks usage for enterprise customer, sends usage information to enterprise@plausible.io",
%{
user: user
} do
billing_stub =
Plausible.Billing
|> stub(:last_two_billing_months_usage, fn _user -> {1_100_000, 1_100_000} end)
|> stub(:last_two_billing_cycles, fn _user ->
{Date.range(Timex.today(), Timex.today()), Date.range(Timex.today(), Timex.today())}
end)
enterprise_plan = insert(:enterprise_plan, user: user, monthly_pageview_limit: 1_000_000)
insert(:subscription,
user: user,
paddle_plan_id: enterprise_plan.paddle_plan_id,
last_bill_date: Timex.shift(Timex.today(), days: -1)
)
CheckUsage.perform(nil, billing_stub)
assert_email_delivered_with(
to: [{nil, "enterprise@plausible.io"}],
subject: "#{user.email} has outgrown their enterprise plan"
)
end
describe "timing" do
test "checks usage one day after the last_bill_date", %{
user: user