Managing Subscriptions
Viewing subscriptions
Section titled “Viewing subscriptions”The Subscriptions page shows all subscription plans with their current status, customer, amount, and next billing date. Use the status segments to find a particular state, including the two a checkout link introduces: Incomplete (a link issued but not yet completed) and Link expired (its 30-day window ran out first).
Issuing a checkout link
Section titled “Issuing a checkout link”New subscription plan on this page issues a customer-facing checkout link rather than starting billing directly; see Subscriptions for the full flow. In short: Plirin does not send the link anywhere, you deliver it to the customer yourself, and the subscription stays Incomplete until they open it and connect a wallet.
Actions
Section titled “Actions”Pause a subscription
Section titled “Pause a subscription”Pausing stops invoice generation until you resume. The customer is not billed during the pause. Navigate to the subscription and click Pause.
Resume a subscription
Section titled “Resume a subscription”Click Resume on a Paused or On Hold subscription. Resuming does not change the subscription’s current period end: if that date has already passed (for example after a long pause), the next invoice generates on the renewal worker’s next pass rather than waiting for the original schedule to come back around.
Cancel a subscription
Section titled “Cancel a subscription”Cancelling stops the subscription for good, but at the end of the current period by default, not instantly: the customer keeps the time they have already paid for, and the subscription stays Active with a cancellation flag set until that period ends, at which point it is cancelled outright and never billed again. Pass cancelAtPeriodEnd: false (GraphQL) or cancel_at_period_end=false (REST and both SDKs) to end it immediately instead. Either way, already-sent invoices remain valid and payable. Click Cancel and confirm.
Cancelling an Incomplete subscription always takes effect immediately, whatever you pass: a checkout link nobody has completed has no period to cancel at the end of, so the link is killed on the spot.
Update a subscription
Section titled “Update a subscription”You can move a subscription to a different recurring price mid-cycle via the API or an SDK (changeSubscriptionPlan on GraphQL, POST /api/v1/subscriptions/{id}/change-plan on REST, see Change a subscription’s plan). The change takes effect immediately, not on the next billing cycle, and settles the time already paid for by proration. There is no dashboard control for this yet.
Subscription invoices
Section titled “Subscription invoices”Each billing cycle generates a standard invoice. You can view all invoices for a subscription in the subscription detail page. Each invoice follows the normal invoice lifecycle.
Automatic collection and mandates
Section titled “Automatic collection and mandates”A subscription collects automatically only while it holds an active mandate: the on-chain authorization its customer signed from the checkout link. Automatic collection is available on Solana today; see Subscriptions for the full chain list and why Ethereum mainnet is permanently excluded.
Inspecting a mandate
Section titled “Inspecting a mandate”GET /api/v1/subscriptions/{id}/mandate (REST, and subscriptions.mandate() / subscriptions.mandate in the SDKs) returns the subscription’s most recent mandate: the chain and token, the customer’s wallet, the per-period cap, the authorized total, how many periods have been authorized and consumed, and its status. On GraphQL the same thing is the mandate field on Subscription.
Two things to expect:
- A mandate that has been revoked, exhausted or superseded is still returned, with that status. Only a subscription that never had a mandate at all is treated as absent (a 404 on REST,
nullon GraphQL). “What happened to this subscription’s mandate” always has an answer. - The
mandatefield on GraphQL needs the same permission as revoking, not the one the rest of the subscription’s fields need, because it exposes wallet addresses. A Cashier querying it gets an error on that field specifically while the rest of the query still resolves.
Revoking a mandate
Section titled “Revoking a mandate”POST /api/v1/subscriptions/{id}/mandate/revoke (REST), revokeSubscriptionMandate (GraphQL), or revokeMandate() / revoke_mandate() in the SDKs stops Plirin collecting and drops the subscription back to sending invoices. It does not cancel the subscription: a revoked mandate is not a cancellation request.
Read this part carefully, because it is the one thing about mandates that surprises people. A merchant-side revocation is Plirin-side only. On Solana, Plirin holds the delegate on the customer’s token account, not the account itself, and a delegate cannot revoke itself. So revoking here marks the mandate so Plirin stops attempting to collect, and nothing more: the customer’s wallet keeps a live delegated allowance to Plirin until the customer revokes it themselves on chain, with an SPL Revoke or by approving a new delegate for zero. If a customer wants the authorization gone, tell them to do it from their wallet.
Revoking twice is not an error. Calling revoke on a mandate that is already revoked, exhausted or superseded succeeds and changes nothing. Calling it on a subscription that never had a mandate is a 400, since nothing there could be read as “already revoked”.
One revocation can affect more than one subscription
Section titled “One revocation can affect more than one subscription”A Solana token account holds exactly one delegate. If a customer pays you for two subscriptions from the same wallet and token, both ride that single authorization, so revoking either one revokes both: each affected subscription falls back to sending invoices and emits its own subscription.mandate_revoked webhook.
Your revocation stops at your own subscriptions. If the same customer also pays an unrelated business from that wallet, that business’s mandate is untouched and keeps collecting. That is deliberate: a merchant-side revoke changes nothing on chain (see above), so there is no chain fact that would justify ending someone else’s collection.
The one case where a revocation genuinely does cross businesses is when the customer revokes, from their own wallet. That really does end the delegation for everyone riding the account, and Plirin marks every affected mandate revoked the next time it reads the account, whoever the merchant is.
Dunning: what happens when a customer misses payment
Section titled “Dunning: what happens when a customer misses payment”Every subscription invoice gets a 7 day payment window (SUBSCRIPTION_INVOICE_DAYS_UNTIL_DUE in the backend). Dunning is gated on that invoice’s due date, not on the next period boundary: reaching the next period with the prior invoice still unpaid does not by itself dun the subscription. If the unpaid invoice is not yet due, the subscription is simply left as it is, active, with its period unadvanced, until the invoice is either paid or its due date passes. For a short cadence like a daily or weekly plan this “waiting” state is the normal one most of the time, since the 7 day window usually outlasts the period itself.
Once an outstanding invoice actually passes its due date without being paid, Plirin does not cancel the subscription immediately. It retries collection up to four times, spaced further and further apart:
| Failed attempt | Status after the failure | Next retry |
|---|---|---|
| 1st | Past Due | 1 day later |
| 2nd | Past Due | 3 days later |
| 3rd | Past Due | 5 days later |
| 4th | Past Due | 7 days later |
| 5th | On Hold | none, retrying stops |
Each offset is measured from the attempt that scheduled it, not added cumulatively from the first failure: the 4th retry lands 7 days after the 4th failed attempt, not 16 days after the 1st. Every failed attempt increases the subscription’s failed_charge_count by one and dispatches a subscription.payment_failed webhook; the 1st through 4th also dispatch subscription.past_due since a retry is still scheduled.
If the 5th attempt (the one following the fourth retry) also fails, the subscription moves to On Hold and a subscription.on_hold webhook fires instead. Plirin stops retrying automatically at that point: an On Hold subscription needs a human, or the customer, to act. Treat On Hold the same way you would treat Past Due for the purpose of deciding whether to keep serving the customer; both mean the customer is behind on payment.
Recovering from Past Due or On Hold
Section titled “Recovering from Past Due or On Hold”There is no separate “retry now” action. What normally recovers a subscription is the outstanding invoice actually getting paid, whether the customer pays it themselves, or you record an offline payment against it. The moment that invoice’s balance reaches zero, the subscription returns to Active, its failed-attempt counter resets to zero, and no further retry is scheduled. A subscription that is Paused or already Cancelled does not participate in dunning at all: a failed or recovered charge against either has no effect on its status.
Resume is the one other way out, and only for On Hold. Clicking Resume on an On Hold subscription returns it straight to Active and clears the same dunning counters, without any payment: an operator override, not a recovery. The outstanding invoice is still outstanding afterwards. Plirin never re-selects an On Hold subscription on its own, so paying the invoice and this override are the only two exits.