Subscriptions
Recurring billing — the state machine, the retry schedule, and what happens between renewals.
A Subscription represents a recurring agreement: charge a customer a fixed amount on a fixed interval. MintCash handles billing, retries on failure, and emails the customer at each state change.
The state machine
Terminal states: failed, cancelled. Once either is reached, no more billing happens.
Allowed transitions
| From | Allowed to |
|---|---|
created | pending, active, failed |
pending | active, failed |
active | past_due, cancelled |
past_due | active, cancelled |
failed | (terminal) |
cancelled | (terminal) |
Full lifecycle
Renewal logic
When a subscription reaches its billingIntervalDays since the last successful charge, MintCash:
- Charges the saved payment method using the stored card token.
- On success: subscription stays
active, firessubscription.succeeded. - On failure: subscription moves to
past_due, firessubscription.failed, and schedules the next retry.
Retry schedule
A failed renewal isn't the end. MintCash retries on a backoff schedule:
| Attempt | When | Status |
|---|---|---|
| 1 | Day 0 (renewal due date) | First charge attempt |
| 2 | +1 day after attempt 1 | Retry 1 |
| 3 | +3 days after attempt 2 | Retry 2 |
| 4 | +5 days after attempt 3 | Retry 3 |
| 5 | +7 days after attempt 4 | Retry 4 |
| — | After all retries fail | Subscription cancelled |
The customer receives an email after the first failure prompting them to update their payment method. If they do, the next renewal cycle starts fresh.
Why this schedule
The 1→3→5→7-day cadence is calibrated against industry decline-recovery patterns. Most recovered subscriptions come back in the first two attempts; the longer tail catches cards reissued mid-month.
Cancellation
Send DELETE /subscriptions/{id} to cancel immediately. Pending invoices are not charged. The subscription's status becomes cancelled — terminal, no further renewals or retries.
Cancellation does not refund prior payments. To refund a customer who cancels mid-cycle, issue a refund against the most recent payment with POST /payments/{id}/refund.
What fires when
| Transition | Webhook | Customer email |
|---|---|---|
created → active | subscription.succeeded | "Subscription active" |
active → past_due | subscription.failed | "Payment failed — update method" |
past_due → active | subscription.succeeded | "Payment recovered" |
* → cancelled | subscription.cancelled | "Subscription ended" |