# The subscription lifecycle

A subscription in Alunta moves through a small number of states, and most support questions about
"why does this subscription look wrong" are really questions about which state it is in and what
that state does to billing.

## The four states: pending, active, under_cancellation and cancelled

A subscription's status is always one of four. The terms subscription statuses and subscription
states refer to these four; there are no others.

- **pending** - the start date is in the future and there is no end date. The subscription exists but
  has not started. It can still have been invoiced already: an invoice-paid subscription with a
  negative day offset is invoiced ahead of its first period.
- **active** - it has started and has no end date. Status says nothing about renewal, so a
  subscription on a plan that does not renew automatically is `active` too, right up to its last
  period.
- **under_cancellation** - it has been cancelled but has not ended yet. The end date is today or in
  the future, and the subscription keeps running and billing until that date.
- **cancelled** - the end date has passed. This is the ended state, not the "someone pressed
  cancel" state.

The distinction between `under_cancellation` and `cancelled` is the one integrators get wrong most
often. A customer who cancels today usually keeps their subscription for weeks, and it is still a
paying subscription for that whole time.

## Trials and pauses are not states

A trial and a pause are periods on the subscription, not statuses of it. Both appear in the timeline
as periods priced at zero, and both leave the subscription itself `active`.

That is deliberate: the timeline must have no unexplained gaps, so a paused month is a real period
with a price of zero rather than a hole between two invoices. This is also why a paused subscription
counts as active but not as billable, and why those two numbers can legitimately differ for the same
customer.

## Cancelling

Without a notice period, a cancellation by default takes effect at the end of the current billing
period: the end date is normally set to the day before the next renewal, and the subscription stays
in `under_cancellation` until then. If the subscription has a notice period (see the next section),
the end date can be later. If a later period has already been invoiced or charged, or has a
card capture in flight, the end date moves forward to the end of that protected period. The
customer keeps what they paid for. This protection applies to ordinary, non-installment periods.
With installments, each settled slice is preserved and remains invoiceable, but it does not keep
the subscription active through the whole parent period.

An immediate cancellation is also possible, but the end date is not always today. Periods that have
already been invoiced or charged, or that have a card capture in flight, are protected, so the end
date moves forward to the end of the last protected period. An immediate cancellation can never
delete an ordinary period the customer has already paid for. Installment slices follow the separate
rule above and do not extend the subscription through their whole parent period.

A cancellation can also be given a specific end date (in the team's dialog and, with `end_date`, in
the API). That day is the last active day; it cannot be before a protected period ends, and in the API
it cannot be in the past. A date part-way through a billing period gives a prorated final period.

Periods that are not yet invoiced or charged and fall after the end date are removed, because they
will never be billed. That is why a cancelled subscription usually has fewer future periods than it
had a moment earlier.

A scheduled plan change follows the same rule as the periods: if it takes effect after the end
date, it is removed together with those periods. If it takes effect on or before the end date, which
a notice period often causes because the subscription keeps running past the next renewal, the
change stays. The periods up to the end date are then billed on the new plan and its interval, and
the change is applied on its effective date even though the subscription is under cancellation.
It does not matter for the end date whether such a change exists. A new plan change cannot be
scheduled on a subscription that is already cancelled.

## Notice period

A subscription can have a notice period: a rule for how long it keeps running after it is cancelled.
The team sets the notice period on the plan, and it is copied to each subscription when the
subscription is created. Without a notice period, the rules in the section above apply unchanged. A
notice period is not a minimum term; it does not set a minimum period from the subscription's start.

**A notice period can only extend.** The end date is the latest of the notice period's end date, the
end of the current period, and the end of any protected periods (a later period that has already
been invoiced or charged). A notice period therefore never produces an earlier end date than a
cancellation without one, and a protected period can still produce a later end date than the notice
period. The cancellation day is today's date in the team's timezone.

There are four types. The examples assume a cancellation on 10 October 2026 on a monthly
subscription with fixed periods (the 1st to the last day of the month), where October has already
been invoiced.

- **Current billing period + N periods** (`billing_periods`, N from 1 to 12). The end date is the end
  of the Nth billing period after the current one. With N = 1 the last day is 30 November 2026.
- **Current month + N months** (`calendar_months`, N from 0 to 24; 0 means "current month"). The end
  date is the last day of the calendar month N months after the month of cancellation. With N = 1 the
  last day is 30 November 2026. If the subscription has periods from the 15th to the 14th, it still
  ends on 30 November, and the last period (15 to 30 November) is priced pro rata.
- **N months' notice to the end of a billing period** (`months_to_period_end`, N from 1 to 24). The
  end date is the end of the first billing period that ends on or after the cancellation day plus N
  months. With N = 3, the cancellation day plus three months is 10 January 2027, so the last day is
  31 January 2027.
- **N days or months from the cancellation day** (`fixed_length`, 1 to 365 days or 1 to 24 months).
  The end date is the cancellation day plus N days, or plus N months without overflow, so 31 January
  plus one month gives 28 February. With 30 days the last day is 9 November 2026, and the period 1 to
  9 November is priced pro rata (9 of 30 days).

The two period-based types (`billing_periods` and `months_to_period_end`) read the period boundaries
from the same calculation that bills the subscription, so fixed and flexible periods, aligned
renewals and scheduled interval changes are taken into account. Pause and trial periods do not count
as billing periods, so a pause neither uses up the notice period nor shortens it. With fixed periods,
a period that a pause splits in two (1 to 4 October, a pause, then 26 to 31 October) counts as only
one. A fragment that a pause cuts off before its billing period is complete, with nothing after the
pause completing it (for example a flexible period, or a pause that runs past the end of the month),
was not paid in full and does not count; the next period after the pause counts instead. With
flexible periods, the first period after a pause is a new, full period, so the customer still pays
for the same number of periods. The end date can, however, fall later or earlier than it would without the pause, because the grid of periods shifts; it is the number of paid periods that is preserved. The two
calendar-based types (`calendar_months` and `fixed_length`) are plain date arithmetic on the
cancellation day, so a pause does not affect them at all.

The notice period is never shorter than the current period, for every type: on an annual subscription
with calendar-year periods, a 30-day notice period cancelled on 10 October 2026 still ends on
31 December 2026, because the current period cannot be shortened.

**If the subscription ends in the middle of a period,** the last period is priced pro rata by days:
the full price of the period times the days the subscription runs, divided by the days in a full
period. This applies to periods that have not yet been invoiced or charged. A period that has
already been invoiced or charged is not re-priced; it is protected.

**The notice period does not apply during a trial, nor to a subscription that has not started
yet.** If the cancellation day falls inside a trial, or before the subscription's start date, the
notice period is not used, and the subscription ends as it would without one, typically when the
trial ends. Otherwise the customer would be charged for a period they never chose; this applies, for
example, to a bundle subscription that never started.

**A notice period that cannot be applied gives an error, not a shorter notice period.** There are
two causes. If the notice period stored on the subscription cannot be read, the subscription is
shown without one (the API shows `notice_period: null`, so `null` does not always mean that a
cancellation will use no notice period). And if a pause runs so far ahead that the notice period
cannot be placed on real billing periods within ten years, the date cannot be calculated. In both
cases a cancellation "with the notice period" is refused instead of giving an earlier end date than
agreed: the API answers 422 and advises `immediate: true`, and the portal explains it in the
cancellation dialog. This also applies to a bundle cancellation, where one such subscription refuses
the whole cancellation (nothing is cancelled, and the message names the subscription so it can be
cancelled on its own first). Immediate cancellation still works, and the team's own dialog explains
the problem and still offers the end of the current period, a specific date and today, so the team
can choose the end date itself.

**The notice period is locked at creation.** The subscription gets the plan's notice period the
moment it is created, whether that happens from the interface, the API, checkout or a bundle. If
the team later changes the plan's notice period, it applies only to new subscriptions. Existing
subscriptions keep theirs, and subscriptions created before the feature existed have no notice
period. A plan change keeps the subscription's notice period, and so does a transfer. Subscriptions
imported from Stripe or e-conomic never get a notice period, even when the plan they are matched to
has one, because the agreement existed before Alunta and must not gain terms retroactively. A buyer in
checkout gets the notice period they were shown (see the checkout guide). The API shows the notice period as `notice_period`
on the subscription: either `null` (no notice period) or an object with `type`, `length`, `unit` and
a readable `label`.

**Who is bound by the notice period:**

- The customer portal always applies the subscription's notice period when the end customer cancels
  themselves. The customer cannot opt out of it, and the dialog shows the end date before the
  customer confirms. The subscription card shows the notice period, with "(applies after the trial)"
  or "(applies once the subscription has started)" when it does not apply yet. If the end date has
  changed since the page was opened (for example across midnight), nothing is saved: the portal shows
  the new date and the customer confirms again. The portal only allows cancelling if the team has
  enabled self-service cancellation.
- The API (`POST /subscriptions/{uuid}/cancel`) applies the notice period by default.
  `immediate=true` overrides it and gives an end date of today; protected periods can still push the
  date forward here too. The end date in the response is the one that applies.
  `GET /subscriptions/{uuid}/cancel-preview` (same optional `immediate` query parameter) returns the
  end date a cancellation would store without cancelling anything: no write and no event. It is
  calculated by the same code as the cancellation and refused with the same `422` messages. It also
  returns the subscription's `notice_period` and `notice_applied`, which is true only when the notice
  period decides the date (it runs later than the end of the current or a protected period); it is
  false without a notice period, with `immediate=true`, in a trial or before the start, and when the
  date would be the same without the notice.
- The team's own cancellation dialog offers four choices when the subscription has a notice period:
  with the notice period (the default), at the end of the current period (the team waives the notice
  period), a specific date, or today. The two choices with a calculated date (with the notice period and at the end of the current period) show the date that will be stored; with "today" and "a specific date" the team picks it. If the date
  changes while the dialog is open (for example across midnight), nothing is saved and the dialog
  shows the new date to confirm. A specific
  date can fall before the notice period ends (the dialog warns that the customer's notice period is
  then waived), but not before a protected period ends. Without a notice period the dialog is as
  before, and "at the end of the current period" shows the date that is stored, even when a later
  period is protected.
- When the team or the API cancels a bundle, each subscription applies its own notice period, so
  the subscriptions in the bundle can get different end dates.
- System cancellations do not follow the notice period. A cancellation caused by payment failure
  uses its own rule, and deactivating Leverandørservice cancels the subscriptions at the end of the
  current period.
- The end customer gets a cancellation confirmation by email with the subscription's name and last
  day (and the notice period, when it decided the date) when the customer cancels in the portal or
  the team or the API cancels. A bundle cancellation sends one email covering every subscription it
  cancelled, with their end dates. If the end date is changed later, a new confirmation with the new
  date is sent. Nothing is sent for a payment failure (it has its own email), a deletion or the
  deactivation of Leverandørservice. The email is on by default and can be switched off under
  Settings → Customer emails (it starts switched off for teams that had switched off the
  subscription confirmation, and for API-provisioned teams until they switch it on); it only goes to
  a customer who has an email address and has not opted out of the email type. Changing the end date
  of a subscription that ended for a reason other than cancellation, or saving the same date again,
  sends no new confirmation.

## How do I cancel a subscription?

Open the subscription's actions and choose cancellation. Without a notice period, a normal
cancellation usually takes effect at the end of the current billing period; if the subscription has
a notice period, it is the default choice and the end date can be later. The status remains
`under_cancellation` until that end date. Choose immediate cancellation only when that is intended.
For ordinary non-installment periods, periods already invoiced or paid, or with a card capture in
flight, are protected and can move the end date forward. With installments, a settled slice remains
preserved and invoiceable but does not keep the subscription active through the whole parent period. Undo the cancellation before
the end date to clear it and rebuild future periods. Cancellation does not automatically refund
money; refunds or credits are handled as a separate financial action.

## Changing terms and rebuilding periods

Rebuilding a subscription's billing periods preserves existing periods that have already ended in
the team's timezone, even if they have not yet been invoiced. A later change to price, quantity,
interval or renewal grouping does not rewrite those periods. Missing periods can still be rebuilt.

Quantity changes and immediate tier upgrades during a billing period keep that period's agreed
base amount and account for the remaining days through a separate prorated adjustment. Later
periods use the new terms.

Cancellation can still shorten an unbilled period that crosses the subscription's new end date.
Deactivating a customer also clears unbilled periods on its frozen subscriptions so they cannot be
invoiced. These operations have different purposes from changing a running subscription's terms.

## Resuming a cancelled subscription

There are two different operations here, and they are easy to confuse.

**Undoing a pending cancellation.** While the subscription is still running towards its end date, the
cancellation can be withdrawn. The end date is cleared, the status returns to `active`, and the
future periods are rebuilt. This is what `POST /subscriptions/{uuid}/resume` does, and it only works
on a subscription that has not ended yet.

**Restarting a subscription that has already ended.** In Alunta this is also possible, but only for
invoice-paid subscriptions, and only from the interface rather than the API. You pick the date it
should start again, and if that leaves a gap after the old end date, the gap is recorded as a pause
so the timeline stays unbroken. A card-paid subscription that has ended cannot be restarted this
way; that customer needs a new subscription.

## Lifecycle webhooks

Alunta processes lifecycle events every hour, so `subscription.started` and `subscription.ended`
normally arrive shortly after the moment they describe rather than at exactly that second. Event
generation uses an atomic database claim, so concurrent lifecycle runs cannot create separate
events for the same logical transition. The committed claim is durable and a reconciler retries it
if the original process or queue path fails before completion. Webhook delivery is still at least
once, not exactly once: a retry or manual replay can deliver the same transition more than once.
Both events include a stable `data.event_id` that stays unchanged across retries and recovery
replays; use it as your idempotency key.

These events are not a complete feed either. The hourly scan skips a subscription entered after its
own start date, so it does not fire `subscription.started` retrospectively. For an ended subscription,
it skips the event only when the membership's last update date is after its end date, which marks the
end as retroactively recorded. Merely scanning a normally scheduled end date after it expires does
not skip it. The normal cancellation action is a separate path: if it resolves to a date that has
already ended, it emits `subscription.ended` immediately. Read the subscription when you need the
full picture.

Trials are tracked on the same hourly run, but they have no public webhook. If you need to react to
a trial starting or ending, poll the subscription rather than wait for an event that will not
arrive.

`subscription.ended` also carries a `reason`, which distinguishes an ordinary cancellation from one
caused by payment failure, by the customer cancelling themselves, or by the subscription or customer
being deleted.

## Bundles do not have a lifecycle

A bundle is a record of a purchase, not a container that owns its subscriptions. Each subscription
in a bundle is independent from the moment it is created and can be cancelled, ended, transferred or
resumed on its own. A bundle can therefore have all, some or none of its original subscriptions left,
and its status is derived from whatever children still exist rather than stored on the bundle.

If you need to know the state of a particular subscription, read that subscription. Never infer it
from the bundle.

## Checking this for your own team

The rules above are the same for every team. The concrete state of a given customer's subscriptions
is data: use `get_customer_subscription_overview` or `list_subscription_customers` for the team you
are connected to.

## Subscription confirmation

When the team creates a subscription itself, the customer can get the subscription confirmation email (Settings > Customer emails): the subscription, start date, price, renewal and notice period, without an invoice - the first invoice is its own email. In the app it is a choice under Create subscription that the team ticks for each creation (it is not ticked in advance, so customers moved over from another system get no welcome email); through the API it is sent only when the request has `send_confirmation: true`. A bundle gets one confirmation listing each subscription. It is never sent for a subscription whose start date is before the day it was created, when the team has switched the email off or the customer has opted out. A checkout purchase gets the order confirmation instead.

## Interrupted manual creation

When you add subscriptions on a customer's page, the order is saved so you can continue after an
interruption. Reopen creation for the same customer using the same user account to see the saved
order. Subscriptions already created are reused, and any invoicing resumes with the saved invoice
basis. An order with several items may be partially completed; an error does not necessarily mean
that nothing was created.

If a product or the invoice basis has changed, continuing may require your review. "Start a new
order" keeps subscriptions already created and stops creating the remaining items or starting
their invoicing. Notifications and lifecycle updates for purchases already committed can still
finish in the background. It does not cancel subscriptions or credit invoices.

## Renewal and upcoming invoicing

A renewal date is the date on which a new period can begin. Upcoming periods show the expected date, currency, and price, but are not a payment or a guarantee that external accounting has completed. A plan can be active without automatic renewal, and an automatic renewal can have a pending or declined payment.

## Trial, introductory price, and pause

A trial is a zero-priced period before regular invoicing. An introductory offer is a time-limited price on new purchases, and an individual discount changes one agreement. A pause is also a zero-priced period, but it is inserted into an existing subscription timeline. None of these are additional subscription statuses.

## Bundles and instalments

A bundle combines the purchase, but every subscription created from it has its own lifecycle. The bundle's one-off amounts are on the same invoice as the first period, like a standalone subscription's one-off lines. Only when no subscription in the bundle has a fixed paid period from the start date (trial, usage or free) do the one-off amounts receive an invoice of their own, no earlier than the start date. The same happens when the subscription that was to carry them is cancelled or deleted before its first invoice while the bundle's other subscriptions continue; when the whole bundle is cancelled, the one-off amounts are not invoiced. When a failed payment has ended one of the bundle's subscriptions, unpaid one-off amounts are not invoiced or charged either; if the subscription is resumed, they are collected again as normal. For a manual purchase starting today or earlier, the one-off amounts instead follow the bundle's first invoice, which the team creates itself. Payment links and offers with a fixed start date follow their own rule, see Checkout and self-service. For a customer paid by a reseller, the one-off amounts are invoiced to the reseller, like the subscriptions. With card payment the one-off amounts are charged together with the first period, so the invoice matches the charge; without a period to ride on they are charged on their own on the start date. Instalments divide one service period into slices with separate due dates and payment lines; due lines in the same currency can be consolidated on one invoice. The instalments are not separate subscriptions.
