Concepts
Subscription lifecycle
How a subscription starts, renews, falls past due and ends, when Fianto retries a renewal, and what each kind of cancel changes.
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.
Scroll sideways to see the whole diagram →
Show as text
- A subscription starts `ACTIVE`; the first period is charged inside the subscribe transaction.
- Fianto charges each renewal every 30 days or every 365 days, as the price sets.
- The first counted failed renewal moves it to `PAST_DUE`; a later successful retry moves it back to `ACTIVE`.
- The 4th counted failure ends it: `ENDED` with reason `PAYMENT_FAILED`.
- 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.
- Re-subscribing ends the old subscription with `PAYER_CANCELED`.
- No back-billing: missed periods are never billed.
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
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.
Scroll sideways to see the whole diagram →
Show as text
- 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.
- Counted failures are `INSUFFICIENT_FUNDS`, `DELEGATION_REVOKED`, `ACCOUNT_FROZEN` and `ACCOUNT_CLOSED`. Each one sends `subscription.payment_failed`.
- The first counted failure moves the subscription to `PAST_DUE` and sends `subscription.past_due` once.
- Any other failure is not counted: Fianto retries it every 5 minutes.
- A successful charge moves the subscription back to `ACTIVE`.
- The 4th counted failure ends it: `ENDED` with reason `PAYMENT_FAILED`.
- 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.
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: nowends the subscription at once and sendssubscription.ended,end_reasonMERCHANT_CANCELED.at: period_endsetscancel_at_period_endand sendssubscription.cancel_scheduled. Asking again while a cancel is scheduled sends no second event. If the payer scheduled that cancel, the merchant's request still setsmerchant_cancel_requestedtotrue.
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_scheduledwithMERCHANT_CANCELED.
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
Was this page helpful? Tell us
Payment lifecycle
How a checkout session, its order and each payment move between statuses, which events each move sends, and why no status is final too early.
Finality
Why Fianto shows the payer success at confirmed but marks an order paid and sends webhooks only at finalized, and what counts as proof of payment.