Repository navigation
feat(billing): plan limits, paid-through entitlement end and store-checked variants - #39
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
CourseLit's plan tiers (Pro and Business) need per-plan allowances, and a security review of the plan found that the billing package grants paid access for longer than it should. A local smoke test with Lemon Squeezy also found that a variant from another store on the same account passes the catalog check, so checkout sells in that store and its webhooks are then ignored as foreign.
What
planLimitsatcreateBilling, read withgetLimit()andlimits().offers.null(unlimited).0. An undeclared key throwslimit_key_unknown, so a typo never reads as unlimited.ossmode is unlimited. A billing read error throws (unlikehasFeature, which fails closed tofalse).paidThroughAtplus a grace period.active,trialingandpast_duestop granting their plan oncepaidThroughAtplusentitlementGraceMs(default seven days) has passed. Before this, a lost renewal, failure or cancellation webhook left an entity entitled for good. A paid status with nopaidThroughAtkeeps today's behaviour.pausedgrants nothing. It maps tocancelledwith no paid-through date; un-pausing moves it back toactive, which the transitions already allow.retrieveProductreads the variant's product and rejects one from another store (provider_product_wrong_store).README, architecture doc and a changeset (minor) are updated.
Behaviour change for existing consumers
SendLit, MediaLit and CourseLit get the stricter entitlement end on upgrade: a subscription whose
paidThroughAtis more than seven days in the past stops counting as paid even if the provider still reports it asactive. Normal renewals movepaidThroughAtforward, so this only bites when webhooks were missed.Test plan
packages/billing: 161 tests pass, including new ones for limits (allowance, unlimited, no plan, lapsed plan, unknown key, read error, oss, key and plan validation), the grace window, paused subscriptions and the wrong-store variant.biome lintpasses (pre-commit hook).