Skip to content

Canceled plan renewed

A customer can cancel a plan at any time. The plan runs to the end of the paid period, and until then the customer can resume it. At the end of each period a renewal job charges every live plan. “Live” was taken to include plans that are canceling, so a plan canceled during the period is charged again and made active at the next renewal.

src/subscription.ts
// A subscription: a canceled plan runs to the end of its period, and the customer can resume it until then.
import { machine } from './vendor/stateproof/packages/core/src/index.ts';
export const subscription = machine('Subscription')
.states('trial', 'active', 'canceling', 'canceled')
.initial('trial')
// Flat data next to the state: set when the customer cancels.
.context({ canceledByUser: false })
.transition('activate', { from: 'trial', to: 'active' })
.transition('cancel', {
from: 'active',
to: 'canceling',
action: (ctx) => {
ctx.canceledByUser = true
}
})
.transition('resume', {
from: 'canceling',
to: 'active',
action: (ctx) => {
ctx.canceledByUser = false
}
})
.transition('renew', { from: ['active', 'canceling'], to: 'active' })
.transition('expire', { from: 'canceling', to: 'canceled' })
.transition('resubscribe', {
from: 'canceled',
to: 'active',
action: (ctx) => {
ctx.canceledByUser = false
}
})
// Must hold in every reachable state.
.invariant('canceled plans are not renewed', (ctx) => !(ctx.canceledByUser && ctx.state === 'active'))

See how stateproof finds and fixes it.

Last updated: