The Stripe statuses that suspend a plan are listed again in the operator's billing card and the test kit instead of read from the reconcile #276

Open
opened 2026-10-26 20:58:25 +00:00 by cgalo5758 · 0 comments
Owner

What a contributor runs into

The console sorts each Stripe subscription status into a class:

  • active and trialing deliver the plan.
  • past_due, unpaid and paused keep the plan but suspend what it delivers.
  • canceled and incomplete_expired end it.
  • incomplete is recorded and delivers nothing yet.

The reconcile reads a subscription from Stripe and brings the console's records in line with it. It decides delivery with classifyStatus (internal/fulfillment/reconcile.go:1042-1053). Other code that needs the same answer does not ask classifyStatus. It lists the statuses again:

  • subscriptionStatePrecedence (internal/server/operator_enrollment.go:380) ranks the states of the operator's billing card with the three suspending ones first, and its comment (lines 370-379) restates why.
  • subscriptionState (lines 395-399) treats active and trialing as the healthy pair that a period-end cancellation turns into "canceling".
  • subscriptionPeriodNote (lines 441-453) lists the three suspending statuses (line 442) and the two ending ones (line 443). It uses them to choose between "renews {date}", "ends {date}" and no note.
  • subscriptionMayBeDisplaced (lines 460-466) lists the five delivering and suspending statuses. It uses them to decide whether a subscription still pays for its tiers.
  • CountLiveSubscriptionsByStatus (internal/billing/queries/subscriptions.sql:95-104) counts active and trialing as the subscriptions owed service, for the operator overview's Monthly recurring caption.
  • The test kit's Suspend (internal/testkit/world/plans.go:377-383) accepts only past_due, unpaid and paused, and writes the suspension the reconcile writes for them.

The copies already disagree on one status. The reconcile gives an incomplete subscription nothing (reconcile.go:1050-1051). The card's period note falls silent only for canceled and incomplete_expired, so it reads an incomplete subscription as renewing on its period end.

Why it costs

#255 asks whether a past-due plan should keep delivering for a grace period. Any answer other than suspending at once changes classifyStatus, and every copy has to change with it. If one copy is missed, nothing fails. The operator's card then ranks a delivering subscription with the suspended ones and says it does not renew, or the test kit writes a suspension the reconcile no longer writes. A status that Stripe adds later also lands in each copy's default separately.

Where

internal/fulfillment/reconcile.go, internal/server/operator_enrollment.go, internal/billing/queries/subscriptions.sql, internal/testkit/world/plans.go

Done when

One exported function names the class of each subscription status. The reconcile, the four functions in operator_enrollment.go and the test kit's Suspend read that function. The overview's count takes its statuses from the function, or its comment names the class it counts. A test fails when a status that the schema's check constraint allows (internal/db/migrations/00010_schema_hardening.sql:65-66) has no class.

### What a contributor runs into The console sorts each Stripe subscription status into a class: - `active` and `trialing` deliver the plan. - `past_due`, `unpaid` and `paused` keep the plan but suspend what it delivers. - `canceled` and `incomplete_expired` end it. - `incomplete` is recorded and delivers nothing yet. The reconcile reads a subscription from Stripe and brings the console's records in line with it. It decides delivery with `classifyStatus` (`internal/fulfillment/reconcile.go:1042-1053`). Other code that needs the same answer does not ask `classifyStatus`. It lists the statuses again: - `subscriptionStatePrecedence` (`internal/server/operator_enrollment.go:380`) ranks the states of the operator's billing card with the three suspending ones first, and its comment (lines 370-379) restates why. - `subscriptionState` (lines 395-399) treats `active` and `trialing` as the healthy pair that a period-end cancellation turns into "canceling". - `subscriptionPeriodNote` (lines 441-453) lists the three suspending statuses (line 442) and the two ending ones (line 443). It uses them to choose between "renews {date}", "ends {date}" and no note. - `subscriptionMayBeDisplaced` (lines 460-466) lists the five delivering and suspending statuses. It uses them to decide whether a subscription still pays for its tiers. - `CountLiveSubscriptionsByStatus` (`internal/billing/queries/subscriptions.sql:95-104`) counts `active` and `trialing` as the subscriptions owed service, for the operator overview's Monthly recurring caption. - The test kit's `Suspend` (`internal/testkit/world/plans.go:377-383`) accepts only `past_due`, `unpaid` and `paused`, and writes the suspension the reconcile writes for them. The copies already disagree on one status. The reconcile gives an `incomplete` subscription nothing (`reconcile.go:1050-1051`). The card's period note falls silent only for `canceled` and `incomplete_expired`, so it reads an `incomplete` subscription as renewing on its period end. ### Why it costs #255 asks whether a past-due plan should keep delivering for a grace period. Any answer other than suspending at once changes `classifyStatus`, and every copy has to change with it. If one copy is missed, nothing fails. The operator's card then ranks a delivering subscription with the suspended ones and says it does not renew, or the test kit writes a suspension the reconcile no longer writes. A status that Stripe adds later also lands in each copy's default separately. ### Where `internal/fulfillment/reconcile.go`, `internal/server/operator_enrollment.go`, `internal/billing/queries/subscriptions.sql`, `internal/testkit/world/plans.go` ### Done when One exported function names the class of each subscription status. The reconcile, the four functions in `operator_enrollment.go` and the test kit's `Suspend` read that function. The overview's count takes its statuses from the function, or its comment names the class it counts. A test fails when a status that the schema's check constraint allows (`internal/db/migrations/00010_schema_hardening.sql:65-66`) has no class.
cgalo5758 added the
kind
debt
area/billingarea/operator-ui
labels 2026-10-26 20:58:25 +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#276