Cancel subscription

Cancel an active subscription.

Source src/operations/cancelSubscription.ts#L38 · handleCancelSubscriptionsrc/operations/cancelSubscription.ts#L83 · assertMayCancelsrc/operations/cancelSubscription.ts#L109 · lifecycleMarker

Cancel subscription

cancelSubscription marks an active Subscription CANCELED, so the background worker's renewal sweep stops billing it. It moves no money.

You submit it with just the subscription id:

let outcome = await economy.submit({
  kind: "cancelSubscription",
  idempotencyKey: "idem_1",
  actor: { kind: "user", userId: "usr_a" },
  subscriptionId: "sub_abc",
});
// outcome.status === "committed"

Canceling forfeits the rest of the period already paid for — there is no refund, so there is nothing to post to the ledger.

Parameters

Every field below is required; cancelSubscription carries no optional payload.

FieldTypeDefaultDescription
idempotencyKeystringMakes a retried request run at most once.
actorPrincipalWho is asking.
subscriptionIdstringThe subscription to cancel. Non-blank.

Returns

cancelSubscription returns an Outcome.

On success the status is committed. The outcome carries a placeholder Transaction with empty legs, because the cancel only flips the subscription's state and records no money moving.

A repeat of the same idempotencyKey returns duplicate. A subscription that is missing or already CANCELED returns rejected with UNKNOWN_SUBSCRIPTION — see Reason codes.

Postings

None. A cancel is a status change only, so the placeholder transaction posts no double-entry legs and advances no account's hash chain.

Authorization

A user actor may cancel only their own subscription. The handler loads the record and compares its owner to the actor's userId; a mismatch throws AUTH.UNAUTHORIZED.

A system or operator actor may cancel anyone's subscription. cancelSubscription is not a privileged-only operation.

Reason codes

cancelSubscription returns one RejectionCode as a rejected outcome — a normal "no", not a thrown fault:

CodeWhen
UNKNOWN_SUBSCRIPTIONNo subscription matches the id, or it is already CANCELED.
ECONOMY_PAUSEDA maintenance window is in effect and the actor is a user. The decline carries resumesAt.

A blank or whitespace-only subscriptionId throws OP.MALFORMED instead — that is malformed client input, caught before the store is touched. A user actor canceling a subscription they do not own throws AUTH.UNAUTHORIZED.

Preconditions and invariants

The ownership check runs only after the subscription is confirmed to exist and be cancelable. So probing a missing or already-canceled id returns the same UNKNOWN_SUBSCRIPTION answer regardless of caller, and never reveals whether the id exists.

Canceling is final for that record: a CANCELED subscription stays canceled, and a second cancel of the same id returns UNKNOWN_SUBSCRIPTION rather than committing again. To bill the same SKU and seller again, the user runs subscribe to open a fresh subscription.

See also