A subscription's next reconcile confers its tier again over the operator's grant that replaced it, and ends the grant #212

Open
opened 2026-10-09 16:08:24 +00:00 by cgalo5758 · 0 comments
Owner

What happens

An operator can issue a grant to an organization whose tier on a ladder is paid for by a subscription. (A ladder is a sequence of tiers such as Plus, Pro and Max. An organization holds one tier on each ladder at a time, from a subscription or from a grant.) The grant's conferral, core.confer, ends the subscription's provision, and the grant takes the ladder. The issue form says the subscription keeps billing until it is changed in the billing view.

The subscription's next reconcile undoes this. reconcileItems compares the products on the Stripe subscription with the subscription's provisions that have not ended. The grant ended the subscription's provision, so its product reads as missing, and the reconcile calls core.confer for it. That conferral ends the grant's provision and puts the subscription's tier back on the ladder. Nothing asks whether another source took the ladder from the subscription.

Any reconcile of an active subscription does this: the customer.subscription.updated event at each renewal or after any other edit, the invoice reconcile, and the sweep's reconcile of a period-end downgrade. A subscription that was past due when the grant was issued does the same when it recovers. SyncSourceStatus resumes nothing, as it should, and the item pass that follows confers the tier again. The test for that case calls SyncSourceStatus directly, so it never runs the item pass.

A grant that is not marked to resume stays recorded as active and never delivers again, even after the subscription ends. A grant marked to resume is recorded as replaced by the subscription, so it comes back only when the subscription is canceled.

#202 is a race in the member's plan change whose last step is this same reconcile. Closing that race leaves this case open.

What should happen

A grant that replaced a subscription's tier keeps the ladder until it ends or is revoked, and the subscription keeps billing, as the issue form says. The subscription management spec says the same for the past-due case: the subscription does not regain the superseded position. A reconcile leaves a ladder alone when another source ended the subscription's provision on it.

Where

internal/fulfillment/reconcile.go: reconcileItems, which lists the subscription's provisions through GetLivePoolProvisionsBySubscriptionID and calls Confer for every product with none. core.confer is in internal/db/migrations/00023_grant_resumption.sql.

Steps

  1. An organization pays for Plus through a subscription.
  2. An operator issues a Max grant to the organization. Max replaces Plus on the ladder.
  3. The subscription renews, and its customer.subscription.updated event is reconciled. Its items have not changed.
  4. Tier changes shows Max ended and a downgrade to Plus by the webhook. The organization is back on Plus, and the Max grant delivers nothing.

A grant of the subscription's own tier ends the same way.

Why it matters

An operator's grant over a paid tier lasts only until the subscription's next event, usually within one billing period, and a grant that is not marked to resume is lost without anyone revoking it.

## What happens An operator can issue a grant to an organization whose tier on a ladder is paid for by a subscription. (A ladder is a sequence of tiers such as Plus, Pro and Max. An organization holds one tier on each ladder at a time, from a subscription or from a grant.) The grant's conferral, `core.confer`, ends the subscription's provision, and the grant takes the ladder. The issue form says the subscription keeps billing until it is changed in the billing view. The subscription's next reconcile undoes this. `reconcileItems` compares the products on the Stripe subscription with the subscription's provisions that have not ended. The grant ended the subscription's provision, so its product reads as missing, and the reconcile calls `core.confer` for it. That conferral ends the grant's provision and puts the subscription's tier back on the ladder. Nothing asks whether another source took the ladder from the subscription. Any reconcile of an active subscription does this: the `customer.subscription.updated` event at each renewal or after any other edit, the invoice reconcile, and the sweep's reconcile of a period-end downgrade. A subscription that was past due when the grant was issued does the same when it recovers. `SyncSourceStatus` resumes nothing, as it should, and the item pass that follows confers the tier again. The test for that case calls `SyncSourceStatus` directly, so it never runs the item pass. A grant that is not marked to resume stays recorded as active and never delivers again, even after the subscription ends. A grant marked to resume is recorded as replaced by the subscription, so it comes back only when the subscription is canceled. #202 is a race in the member's plan change whose last step is this same reconcile. Closing that race leaves this case open. ## What should happen A grant that replaced a subscription's tier keeps the ladder until it ends or is revoked, and the subscription keeps billing, as the issue form says. The subscription management spec says the same for the past-due case: the subscription does not regain the superseded position. A reconcile leaves a ladder alone when another source ended the subscription's provision on it. ## Where `internal/fulfillment/reconcile.go`: `reconcileItems`, which lists the subscription's provisions through `GetLivePoolProvisionsBySubscriptionID` and calls `Confer` for every product with none. `core.confer` is in `internal/db/migrations/00023_grant_resumption.sql`. ## Steps 1. An organization pays for Plus through a subscription. 2. An operator issues a Max grant to the organization. Max replaces Plus on the ladder. 3. The subscription renews, and its `customer.subscription.updated` event is reconciled. Its items have not changed. 4. Tier changes shows Max ended and a downgrade to Plus by the webhook. The organization is back on Plus, and the Max grant delivers nothing. A grant of the subscription's own tier ends the same way. ## Why it matters An operator's grant over a paid tier lasts only until the subscription's next event, usually within one billing period, and a grant that is not marked to resume is lost without anyone revoking it.
cgalo5758 added the
kind
bug
area/entitlementsarea/billing
labels 2026-10-09 16:08:24 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiki-cafe/member-console#212