Cancel subscription
Stop a subscription renewing: the paid period runs out, nothing more is billed, and nothing is refunded.
Source src/operations/cancelSubscription.ts#L30cancelSubscriptionsrc/operations/cancelSubscription.ts#L70assertMayCancelsrc/operations/guards.ts#L166lifecycleMarker
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:
const outcome = await economy.submit({
kind: 'cancelSubscription',
idempotencyKey: 'idem_1',
actor: { kind: 'user', userId: 'usr_a' },
subscriptionId: 'sub_abc',
});
// → { status: "committed", transaction: { id: "txn_…", … } }curl -s https://economy.example/submit \
-H 'content-type: application/json' \
-d '{
"kind": "cancelSubscription",
"idempotencyKey": "idem_1",
"actor": { "kind": "user", "userId": "usr_a" },
"subscriptionId": "sub_abc"
}'Canceling forfeits the rest of the period already paid for. There is no refund, so there is nothing to post to the ledger.
Parameters
The payload fields, beyond the kind tag, are:
| Field | Type | Default | Description |
|---|---|---|---|
idempotencyKey | string | (required) | A retried submit with the same key runs at most once. See idempotency. |
actor | Principal | (required) | Who is asking. A user actor may cancel only their own subscription; a system or operator actor may cancel anyone’s. |
subscriptionId | string | (required) | The subscription to cancel. Non-blank. |
Returns
cancelSubscription resolves to an Outcome.
committed: the subscription isCANCELED. The placeholderTransactioncarries empty legs — no money moves.duplicate: a repeat of the sameidempotencyKey.rejected: the subscription is missing or alreadyCANCELED, withUNKNOWN_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 these reason codes
as a rejected outcome, a normal “no”, not a thrown fault:
| Code | When |
|---|---|
UNKNOWN_SUBSCRIPTION | No subscription matches the id, or it is already CANCELED. |
ECONOMY_PAUSED | A maintenance window is in effect and the actor is a user. The decline carries resumesAt. |
A malformed request throws instead:
| Code | When |
|---|---|
OP.MALFORMED | A blank or whitespace-only subscriptionId. Malformed client input, caught before the store is touched. |
AUTH.UNAUTHORIZED | A user actor cancels a subscription they do not own. |
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.