Skip to content

Add usage notifications for billing limits#6056

Open
sanne-san wants to merge 8 commits intomasterfrom
sanne-billing-notifications
Open

Add usage notifications for billing limits#6056
sanne-san wants to merge 8 commits intomasterfrom
sanne-billing-notifications

Conversation

@sanne-san
Copy link
Contributor

Changes

  • As a first step towards improving the subscription settings page, this commit adds usage notifications for billing limits. These are not yet being displayed in the UI.

Tests

  • Automated tests have been added

Changelog

  • This PR does not make a user-facing change

Documentation

  • This change does not need a documentation update

Dark mode

  • This PR does not change the UI

- As a first step towards improving the subscription settings page, this commit adds usage notifications for billing limits. These are not yet being displayed in the UI.
@sanne-san sanne-san force-pushed the sanne-billing-notifications branch from 4fe93b2 to 0936c29 Compare February 10, 2026 08:36
@sanne-san sanne-san force-pushed the sanne-billing-notifications branch from 4d89e7b to db8fa96 Compare February 10, 2026 09:15
Copy link
Contributor

@RobertJoonas RobertJoonas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like where we're going with this! As you've probably realized, the billing logic is incredibly complex and there are a lot of different considerations at every step. So even a small PR like this has a bunch of comments 😄

PS: The screen recordings you shared are 💯👌! Excited to see those changes in :)

- Move `determine_notification_type` function from Notice component to `Plausible.Billing.Quota`
- Rename `determine_notification_type` to `usage_notification_type`
- Cut down on the number of arguments to make the `usage_notification_type` function more readable
- Add public `has_billing_cycles` predicate function
- Update `exceeds_last_two_usage_cycles` to use `has_billing_cycles`
- Extract `pageview_cycle_usage_notification_type` for pageview limit notifications
- Rename :limits_reached_combined to :site_and_team_member_limit_reached
- Update all tests to use realistic data structures
- Split traffic_exceeded_sustained into base notification and grace_period_active variant
- Add GracePeriod.days_left/1 to calculate remaining days in grace period
- Display "within the next X days" for active grace periods
@sanne-san sanne-san force-pushed the sanne-billing-notifications branch from d0efcd2 to 197febf Compare February 12, 2026 13:27

defp pageview_cycle_usage_notification_type(usage, limit) do
exceeded = exceeded_cycles(usage, limit)
last_exceeded? = is_map_key(usage, :last_cycle) and usage.last_cycle.total > limit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be best to keep using the exceeded cycles function here, just in order to link it to that context so that whenever there's a change related to limit checking, it brings the attention to the subscription settings page as well (because checking limits is relevant here).

My proposal would be to extend the exceeded_cycles function with a third argument opts \\ [] (empty list by default), and then evaulate the actual limit for checking as:

limit =
  if Keyword.get(opts, :with_margin, true) do
    Limits.pageview_limit_with_margin(allowed_volume)
  else
    limit
  end

And then in the call sites (such as our pageview_cycle_usage_notification_type) we could do:

exceeded = exceeded_cycles(usage, limit, with_margin: false)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good idea 👍

not Plausible.Teams.on_trial?(team) and is_nil(subscription) ->
:trial_ended

Plausible.Teams.GracePeriod.active?(team) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on Basecamp, this case needs to be split in two:

GracePeriod.manual_lock_active?(team) ->
  :manual_lock_grace_period_active

GracePeriod.active?(team) ->
  :grace_period_active

In the :manual_lock_grace_period_active case, we should end up displaying this message:

Title: Upgrade required due to sustained higher traffic
Body: To ensure uninterrupted access to your stats, please upgrade to a plan that fits your current usage.

NOTE: The GracePeriod.manual_lock_active? function doesn't exist yet, but we should add it.

Co-authored-by: RobertJoonas <56999674+RobertJoonas@users.noreply.github.com>
Comment on lines +401 to +407
days_left = Plausible.Teams.GracePeriod.days_left(team)

deadline_text =
case days_left do
1 -> "within the next day"
n -> "within the next #{n} days"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think instead of this (and the new function) we can actually use the already existing PlausibleWeb.LayoutView.grace_period_end(team)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was wondering that but the formatting is a bit different (today and tomorrow). Am I OK to change that? It seems like it isn't actually being used anywhere?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right. The function I suggested is not used anywhere anymore since #4652. But we can adjust it and start to use it, sure.

Actually, grace_period_end is in UTC and we compare it with Date.utc_today(), so "today" could actually mean "tomorrow" and vice versa, because the person viewing the message might be in a different timezone. I think in order to be as accurate as possible, we should display the difference in hours instead of days when days_left < 2. WDYT?

Wrote some code here: #6067. Feel free to merge :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants