When a grant over a paid tier ends, the subscription that pays for that tier gets it back only at its next Stripe event, not when the grant ends #270
Closed
opened 2026-10-26 20:58:22 +00:00 by cgalo5758
·
2 comments
Labels
Clear labels
accessibility
area/billing
area/catalog
area/discourse
area/domains
area/entitlements
area/fedwiki
area/identity
area/integrations
area/licensing
area/member-ui
area/meta
area/operator-ui
area/ops
area/testing
duplicate
good-first-issue
invalid
privacy
security
upstream
wontfix
A barrier for people using assistive technology or a keyboard alone.
The Stripe mirror, checkout, subscriptions, invoices, fulfillment.
Products, prices, plan ladders, purchasability.
The Discourse integration.
The domains registry, claims, placements, the certificate ask.
Entitlement sets, rules, grants, pools, provisioning.
The Federated Wiki integration and farm sync.
Sign-in, sessions, persons, organizations, workspaces, roles.
The provider registry, outbox and webhooks in general.
Licenses, the contributor agreement, SPDX headers.
Member pages.
The repository itself, its contributing guide, CI, the tracker and the workflow.
Operator pages, forms, lists, the design system.
Deployment, configuration, migrations, workflows, instance settings.
The test stack, screens, lint, walkthroughs.
Closed because another issue already covers it.
Small, self-contained, and explained enough to be a first contribution.
Closed because it is not a ticket for this repository.
Touches what a person's data reveals.
Touches authentication, authorization, secrets or data exposure.
Waits on another repository or project before it can move.
Closed because it will not be done, with the reason in the last comment.
kind
bug
The software does something other than what it promises; closed when it again does what it promises.
kind
debt
Code, tests or tooling to clean up with nothing visible changing; closed when they are cleaner.
kind
design
A question to settle before work can be defined; closed when the decision is written down.
kind
docs
Documentation that is wrong or missing; closed when it says the right thing.
kind
enhancement
Something the software does not do yet; closed when it does.
priority
critical
Blocks the active milestone or harms members now.
priority
high
Next in line inside the active milestone.
priority
low
Inside the active milestone, when nothing else is left.
priority
medium
Inside the active milestone, after the high ones.
Milestone
No items
No Milestone
Public launch
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: wiki-cafe/member-console#270
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What a person cannot do today
Some terms first. A plan ladder is a ranked sequence of tiers, for example Plus, Pro and Max. An organization holds one tier per ladder at a time: from a subscription it pays for in Stripe, from a grant an operator issues, or from its organization type's default, a free tier the console confers when the organization holds nothing. A reconcile is the console reading a subscription back from Stripe and bringing the organization's tiers into line with it.
An operator can issue a grant over a tier that a subscription pays for. The grant takes the ladder; the subscription keeps billing and delivers nothing there, and its reconcile leaves the ladder alone while the grant holds it (
docs/models/plan-ladders-transitions.md:80).When the grant ends, by revocation or at its end date, that act does not give the subscription its tier back. Revocation and expiry end the grant's delivery, resume any grant marked to resume that the ended grant had replaced, and then, if the organization holds nothing on any ladder, confer the organization type's default (
internal/entitlements/grant_acts.go:294-315and:380-394;internal/entitlements/reapply_defaults.go:84-114). Nothing confers the subscription. It delivers again at its next reconcile; for a subscription nobody edits, that is thecustomer.subscription.updatedevent Stripe sends at renewal (internal/integrations/stripe/workflows/webhook_subscription.go:96). Until then the member pays for the tier, and the Products page shows the default as their plan, or nothing on that ladder when the organization holds another ladder. The same happens when the plan that held the ladder was a second subscription and that subscription ends (reconcileEnded,internal/fulfillment/reconcile.go:213-278).What they should be able to do
A member whose organization still pays for a tier gets it back in the act that ends the plan holding the ladder against it: a revocation, an expiry, or the end of the subscription that held it. That act would confer the paying subscription from its recorded items, the way it already confers a grant marked to resume (
ResumeReplacedGrantsTx,internal/entitlements/grant_resumption.go:85-140), for example after those grants and before the default floor. A subscription whose payment is due would get its tier back suspended, as a past-due subscription's tier is shown today.What the step has to respect:
docs/database-locks.md:138-172). The step has to work from the pool row alone, or an ending act and a reconcile can wait on each other.core.subscription_displacements(internal/db/migrations/00034_subscription_displacements.sql:30). The step reads that statement instead of writing a second one.openspec/specs/grant-resumption/spec.md:314-326), and describes what happens to a grant this subscription had replaced while an operator's grant holds the ladder. The step changes what that requirement covers.docs/models/plan-ladders-transitions.md:80,docs/stripe.md:214anddocs/plan-management.md:164state today's timing, "at its next reconcile".Why it matters
For up to one billing period after a grant ends, a member pays for a tier and receives the free default or nothing on that ladder, and no page says why.
Where
internal/entitlements/grant_acts.go(RevokeGrantTx,ExpireGrantTx);internal/fulfillment/reconcile.go(reconcileEnded);internal/entitlements/grant_resumption.go;internal/db/migrations/00034_subscription_displacements.sql.Done when
After a grant over a paid tier is revoked or expires, the organization holds the subscription's tier again in the same transaction, with no Stripe event; the same holds when a second subscription that held the ladder ends; a test drives each case; and the model page,
docs/stripe.mdand the grant-resumption spec state the new timing.This becomes part of #269's fix. While a grant holds the ladder, the subscription's billing is paused at Stripe; when the grant ends, the same change resumes the billing and delivers the subscription's tier again, so neither waits for the next Stripe event.
When a grant that took a subscription's ladder ends, whether it is revoked, expires, or was itself a subscription that ended, the subscription's tier is now delivered again in the same step, before the organization type's default would be put back. Nothing waits for the subscription's next event at Stripe. A subscription whose payment is due gets its tier back suspended, as it would at its own renewal. A grant that was marked to come back after a later plan replaced it still comes back first, and the subscription then waits for that grant.
Commit:
dacb8b57