# Subscription lifecycle (/concepts/subscription-lifecycle)

A subscription is one payer's wallet paying one price every 30 days or every 365 days, on Solana.
Fianto keeps a record of it with three statuses, `ACTIVE`, `PAST_DUE` and `ENDED`, and sends a
webhook on each change. The payer's subscription on Solana is a separate account, and the two do
not always end together.

**The life of a subscription**

1. A subscription starts `ACTIVE`; the first period is charged inside the subscribe transaction.
2. Fianto charges each renewal every 30 days or every 365 days, as the price sets.
3. The first counted failed renewal moves it to `PAST_DUE`; a later successful retry moves it back to `ACTIVE`.
4. The 4th counted failure ends it: `ENDED` with reason `PAYMENT_FAILED`.
5. From `ACTIVE` or `PAST_DUE`, a payer cancel ends it at the current period end with reason `PAYER_CANCELED`; a merchant cancel ends it now or at period end with reason `MERCHANT_CANCELED`: Fianto stops charging (a charge already in flight can still land), but the payer's on-chain subscription stays live.
6. Re-subscribing ends the old subscription with `PAYER_CANCELED`.
7. No back-billing: missed periods are never billed.

## Start [#start]

The payer signs one subscribe transaction on the hosted checkout page. The first period is charged
inside that transaction. When it finalizes, Fianto creates the subscription as `ACTIVE` and sends
`subscription.created`. The next charge is due one period later, 2 minutes after the period ends.

A subscription keeps the receiving wallet and the service fee it started with. If either changes,
only new subscribers get the new ones.

## Renewals [#renewals]

At each renewal **Fianto's charging key** signs the charge and pays its network fee. The charge
pulls the price to the merchant's wallet and, when the fee is above zero, the service fee to
Fianto's treasury. When a renewal succeeds, Fianto sends `subscription.renewed`.

> **What each renewal takes:**
>
> Each renewal pulls the price to the merchant's wallet and, when the service fee is above zero, the
> service fee on top of it to Fianto's treasury. The payer pays the price plus the fee; the merchant
> receives the price. Fianto's charging key pays the renewal's network fee.

**When Fianto retries a renewal**

1. Fianto's charging key tries each renewal 2 minutes after the period ends, then 1 hour after, 6 hours after and 24 hours after, measured from the period end when nothing delays them. After a delay not caused by the payer, each retry keeps its gap from the last failure.
2. Counted failures are `INSUFFICIENT_FUNDS`, `DELEGATION_REVOKED`, `ACCOUNT_FROZEN` and `ACCOUNT_CLOSED`. Each one sends `subscription.payment_failed`.
3. The first counted failure moves the subscription to `PAST_DUE` and sends `subscription.past_due` once.
4. Any other failure is not counted: Fianto retries it every 5 minutes.
5. A successful charge moves the subscription back to `ACTIVE`.
6. The 4th counted failure ends it: `ENDED` with reason `PAYMENT_FAILED`.
7. No back-billing: missed periods are never billed.

Fianto schedules four attempts for each renewal: 2 minutes after the period ends, then 1 hour, 6 hours and
24 hours after it, measured from the period end when nothing delays them. After a delay that is not
the payer's doing, each retry keeps its gap from the last failure.

Only four kinds of failure are **counted**: `INSUFFICIENT_FUNDS`, `DELEGATION_REVOKED`, `ACCOUNT_FROZEN` and
`ACCOUNT_CLOSED`. Any other failure is retried every 5 minutes and does not use up an attempt,
except a renewal refused because the payer already cancelled on Solana: that is recorded as the
payer's cancel, not retried. While
the merchant or the application is disabled, Fianto puts the renewal off by an hour at a time, and
the subscription does not end for that reason.

| Outcome                                         | Status                                 | Webhooks                                                                           |
| ----------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| The renewal succeeds                            | `ACTIVE`                               | `subscription.renewed`                                                             |
| 1st counted failure of an `ACTIVE` subscription | `PAST_DUE`                             | `subscription.payment_failed` and `subscription.past_due`                          |
| 2nd or 3rd counted failure                      | stays `PAST_DUE`                       | `subscription.payment_failed`                                                      |
| A later retry succeeds                          | back to `ACTIVE`                       | `subscription.renewed`                                                             |
| 4th counted failure                             | `ENDED`, `end_reason` `PAYMENT_FAILED` | `subscription.payment_failed` and `subscription.ended`, no `subscription.past_due` |

On these failure events, `failure.attempt_no` is the number of counted failures for that period so
far, and `failure.next_attempt_at` is `null` when no attempt remains.

There is no back-billing: Fianto never bills a missed period later.

When a subscription is `PAST_DUE` with no cancel scheduled and its latest counted failure was
`DELEGATION_REVOKED` or `ACCOUNT_CLOSED`, the payer can renew the shared approval from the payer
portal. That brings forward the blocked retries of every Fianto
subscription on that wallet, in any shop, and resets no attempts. See
[Failed renewals](/payers/failed-renewals).

## Cancel [#cancel]

A subscription is cancelled either by the payer or by the merchant, and the two are different.

**The payer cancels on Solana**, from the payer portal or from another wallet app. The subscription
ends at the end of the current period, and on a `PAST_DUE` subscription Fianto stops retrying. Fianto
sends `subscription.cancel_scheduled` with `cancel_reason` `PAYER_CANCELED`. A cancel made outside
the portal is picked up within about 5 minutes.

**The merchant cancels in Fianto**, from the dashboard or with `POST /v1/subscriptions/{id}/cancel`:

* `at: now` ends the subscription at once and sends `subscription.ended`, `end_reason`
  `MERCHANT_CANCELED`.
* `at: period_end` sets `cancel_at_period_end` and sends `subscription.cancel_scheduled`. Asking
  again while a cancel is scheduled sends no second event. If the payer scheduled that cancel, the
  merchant's request still sets `merchant_cancel_requested` to `true`.

> **A merchant cancel changes nothing on Solana:**
>
> A merchant cancel only stops Fianto from starting new charges. It cannot be reversed. The payer's
> subscription on Solana stays live, and with `now` a renewal charge already in flight can still
> land. There is no refund API: refund the payer yourself, from your own wallet.

When a scheduled cancel takes effect, Fianto sends `subscription.ended` with the `end_reason` of whoever
cancelled. It waits while a renewal charge is in flight.

A payer can withdraw their own cancel from another app that supports resuming a subscription; the
payer portal has no undo. Fianto sees the resume only at the end of the period. Then:

* with no merchant cancel standing, the scheduled cancel is cleared, renewals go on, and Fianto sends
  `subscription.cancel_withdrawn`;
* when the merchant had also asked to cancel, the cancel stays and becomes the merchant's: Fianto
  sends `subscription.cancel_scheduled` with `MERCHANT_CANCELED`.

## End [#end]

A subscription can go from `ACTIVE` or `PAST_DUE` to `ENDED`, and nothing moves it out of `ENDED`.
The end reasons are:

| `end_reason`        | Why                                                                                            |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `PAYER_CANCELED`    | The payer cancelled, or the payer subscribed again and the new subscription replaced this one. |
| `MERCHANT_CANCELED` | The merchant cancelled.                                                                        |
| `PAYMENT_FAILED`    | The 4th counted failure of a renewal.                                                          |

The API type also lists `PLAN_ENDED` and `PLAN_SUNSET`; Fianto does not set them today. If you ever
receive an end reason you do not handle, treat the subscription as ended.

After a merchant cancel or a failed payment, the payer's subscription on Solana is still open. The
same wallet cannot subscribe to the same plan again until the payer uses **Cancel on Solana** in the
payer portal, waits for the on-chain end and reclaims the rent.

## See also [#see-also]

- [Subscriptions for developers](/developers/subscriptions): Create subscription sessions and cancel from your server.

- [Subscriptions in the dashboard](/merchants/subscriptions): Follow renewals and cancel from the dashboard.

- [The Subscriptions program](/concepts/subscriptions-program): What Solana enforces on every charge.

- [Webhook events](/developers/webhooks/events): What each subscription event carries.