A Checkout page opened before a plan was bought can still buy that plan's ladder a second time #257

Open
opened 2026-10-24 06:24:39 +00:00 by cgalo5758 · 0 comments
Owner

What happens

The console decides whether a member may start Checkout when it creates the Stripe Checkout Session, and it refuses Checkout on a plan ladder the organization already pays for. It sets no expiry on the session (internal/server/billing.go, CheckoutSessionParams has no ExpiresAt), so Stripe keeps the session payable for its default of 24 hours. Nothing checks again when the session completes.

A session opened before the organization's first subscription on a ladder completed therefore stays payable after it. Two Checkout tabs opened side by side, or one left open and finished later, each complete a subscription. Stripe then bills both. A ladder holds one position at a time, so one of the two subscriptions pays for nothing.

What should happen

Once the organization pays for a tier on a ladder, no earlier Checkout Session can complete another subscription on that ladder, and a member cannot be billed twice for one ladder through Checkout.

Where

internal/server/billing.go (the session's parameters); the subscription reconcile in internal/fulfillment/reconcile.go, which confers a completed subscription's product whatever else holds the ladder. Stripe lets a session be expired early through the API, and expires_at can be set as short as 30 minutes.

Steps

  1. As a member of an organization with no plan, open the Products page in two tabs.
  2. Start Checkout for a plan in each tab.
  3. Pay in the first tab, then in the second.
  4. Stripe shows two active subscriptions for the organization's customer, both billing.

Why it matters

A member pays twice for one plan, and nothing in the console shows the second subscription as a problem.

### What happens The console decides whether a member may start Checkout when it creates the Stripe Checkout Session, and it refuses Checkout on a plan ladder the organization already pays for. It sets no expiry on the session (`internal/server/billing.go`, `CheckoutSessionParams` has no `ExpiresAt`), so Stripe keeps the session payable for its default of 24 hours. Nothing checks again when the session completes. A session opened before the organization's first subscription on a ladder completed therefore stays payable after it. Two Checkout tabs opened side by side, or one left open and finished later, each complete a subscription. Stripe then bills both. A ladder holds one position at a time, so one of the two subscriptions pays for nothing. ### What should happen Once the organization pays for a tier on a ladder, no earlier Checkout Session can complete another subscription on that ladder, and a member cannot be billed twice for one ladder through Checkout. ### Where `internal/server/billing.go` (the session's parameters); the subscription reconcile in `internal/fulfillment/reconcile.go`, which confers a completed subscription's product whatever else holds the ladder. Stripe lets a session be expired early through the API, and `expires_at` can be set as short as 30 minutes. ### Steps 1. As a member of an organization with no plan, open the Products page in two tabs. 2. Start Checkout for a plan in each tab. 3. Pay in the first tab, then in the second. 4. Stripe shows two active subscriptions for the organization's customer, both billing. ### Why it matters A member pays twice for one plan, and nothing in the console shows the second subscription as a problem.
cgalo5758 added the
kind
bug
area/billingarea/member-ui
labels 2026-10-24 06:24:39 +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#257