Self-serve subscription state
Accounts that pay by card manage their own subscription — starting one, changing plan, buying add-ons, cancelling and resubscribing. This page covers the two fields that say where such an account stands and what it may do next, so a billing screen never has to work either out from raw status text.
Two fields, two questions
| Question | Field |
|---|---|
| Is this account provisioned, or does it need to pay? | Account.selfServeSubscriptionState |
| Where does its subscription stand, and what can it change? | Account.selfServeAccountSubscription → state and capabilities |
selfServeSubscriptionState covers accounts with no subscription at all — one that never completed checkout, or that is not self-serve in the first place. selfServeAccountSubscription is null for those, so start from selfServeSubscriptionState when you are deciding whether to show a billing screen at all, and read the subscription's own fields once you are on it.
Neither says what the account is entitled to. That stays with the *Available flags on Account.subscription — a subscription can be perfectly healthy and still not include the product you are gating on.
query GetSelfServeBillingState($accountId: ObjectID!) {
getAccount(id: $accountId) {
id
selfServeSubscriptionState
selfServeAccountSubscription {
state
status
cancelAtPeriodEnd
currentPeriodEnd
trialEnd
planCode
planName
capabilities {
canCancel
canReactivate
canResubscribe
canUpdate
}
}
}
}
Subscription state
selfServeAccountSubscription.state is one typed value covering the whole lifecycle. Branch on it rather than on status, which carries the payment provider's own vocabulary and is not enumerated in this schema.
| State | Meaning |
|---|---|
Active | Paid, running, not set to cancel. |
Trialing | In a trial that converts to paid at trialEnd. |
Cancelling | Still live, but set to end. The customer keeps everything until it does. |
PaymentFailed | Payment is outstanding. Usually still granting access while the card is retried — do not read it as a loss of access. |
PaymentIncomplete | The first payment was never completed, so the subscription has never granted anything. |
Paused | Paused on our side; only Lumar can lift it. |
Ended | Over for good, by cancellation or an expired first payment. |
Where a subscription qualifies for more than one, the more urgent wins: a cancelling trial reports Cancelling, and an outstanding payment reports PaymentFailed whatever else is set.
state is deliberately wider than cancelAtPeriodEnd, which is only true when the end date falls on the current period — the usual case, and the one where currentPeriodEnd is the date to show. A cancellation dated anywhere else still reports Cancelling, so read cancelAtPeriodEnd before presenting currentPeriodEnd as the day access stops.
Capabilities
capabilities reports which subscription mutations the subscription's current state allows. Each flag is taken from that mutation's own state precondition, so a screen can render the buttons it is given instead of deriving them from a status string.
They are eligibility, not a guarantee of success: a call can still fail for a reason that has nothing to do with the subscription's state. Resubscribing is the one to watch — it creates a new subscription and cannot inherit the cancelled one's card, so an account with no default payment method gets ACCOUNT_SUBSCRIPTION_PAYMENT_METHOD_MISSING and needs the billing portal, even though canResubscribe was true.
| Flag | Mutation it gates |
|---|---|
canUpdate | updateSelfServeAccountSubscription — plan, billing interval and add-ons all move through this one mutation |
canCancel | cancelSelfServeAccountSubscription |
canReactivate | reactivateCancellingSelfServeAccountSubscription — call off a pending cancellation |
canResubscribe | reactivateCancelledSelfServeAccountSubscription — start again after the subscription ended |
Plan and add-on changes are refused while a cancellation is pending: canReactivate is the way back, and the others become true again once it is called off.
canResubscribe is not simply "the subscription ended". Reactivation applies only to a cancelled subscription; one whose first payment expired instead has to go through checkout again, which selfServeSubscriptionState reports as CheckoutRequired. Read the capability rather than inferring it from Ended.
A note on freshness
state and capabilities describe the subscription as Lumar last recorded it, which is why they cost nothing to read. An account is meant to hold one subscription at a time, but two checkouts completing together can leave it with more, and there the recorded one may not be the one Stripe is billing. If a mutation a capability offered is refused, re-query rather than treating it as final — and start from selfServeSubscriptionState, which asks Stripe directly.