The subscriptions service in Banking handles recurring revenue: plan management, subscription lifecycle, trial handling, and the dunning flow when a card fails. Subscriptions are processed through the configured payment provider (typically Stripe) — the platform stores the plan and subscription records and orchestrates the billing schedule. Each successful charge flows through the same merchant-checkout journal entries as one-off payments.
The Banking subscriptions service is separate from the Storefront subscriptions module. Storefront subscriptions are tied to e-commerce orders and product fulfillment. Banking subscriptions are pure recurring billing — useful for SaaS plans, memberships, and donations. If you're selling products on a recurring schedule, use Storefront. If you're selling access or service that doesn't have a fulfillable SKU, use Banking.
Plans
A plan is the template for a subscription: amount, interval, currency, optional trial.
| Field | Meaning |
|---|---|
planId | Auto-generated plan_<random>. |
name | Display name. |
amount | Price per period. |
currency | ISO 4217. Defaults to USD. |
interval | day / week / month / year. |
intervalCount | Multiplier — interval: month, intervalCount: 3 = quarterly. |
trialDays | Optional free-trial length. |
features | Free-text list, surfaced on checkout pages. |
metadata | Free-form key/value bag. |
Plans are stored under the sf_product data type with productType: "subscription" and recurring: true, so they show up in product listings if you query the Storefront catalog.
Create
/banking/merchant/subscriptions/plansJWT{
"name": "Pro Monthly",
"amount": 29.99,
"currency": "USD",
"interval": "month",
"intervalCount": 1,
"trialDays": 14,
"features": ["Unlimited projects", "Priority support"]
}
List
/banking/merchant/subscriptions/plansJWTFilter with status, page, pageSize.
Get / update
/banking/merchant/subscriptions/plans/{planId}JWT/banking/merchant/subscriptions/plans/{planId}JWTPlan changes affect new subscriptions only — existing subscriptions stay on the plan version they were created against. To migrate existing subscribers, cancel and re-subscribe on the new plan.
Subscriptions
A subscription is one customer billed against one plan, with its own period, status, and payment method.
Status lifecycle
trialing → active → past_due → canceled
↓
paused
| Status | Meaning |
|---|---|
trialing | In free trial; no charges yet. |
active | Currently billed. |
past_due | Last charge failed; in dunning window. |
canceled | Terminal. Either by customer request or after exhausted dunning. |
paused | Temporarily halted; no charges, period clock paused. |
Create
/banking/merchant/subscriptionsJWT{
"customerId": "cust_abc",
"customerEmail": "[email protected]",
"planId": "plan_pro_monthly",
"paymentMethodId": "pm_stripe_xyz",
"trialDays": 14
}
If trialDays is set (or inherited from the plan), the subscription starts in trialing and the first charge is scheduled for trialEnd. Otherwise the first charge runs immediately.
Read
/banking/merchant/subscriptions/{subscriptionId}JWTReturns the subscription with currentPeriodStart, currentPeriodEnd, trialStart, trialEnd, and any cancellation flags.
Cancel
/banking/merchant/subscriptions/{subscriptionId}/cancelJWT{ "atPeriodEnd": true, "reason": "Customer requested" }
atPeriodEnd: true keeps the subscription active until the current period ends, then transitions to canceled — the customer keeps access through what they've already paid for. atPeriodEnd: false cancels immediately and (depending on your refund policy) prorates the unused portion.
Pause and resume
/banking/merchant/subscriptions/{subscriptionId}/pauseJWT/banking/merchant/subscriptions/{subscriptionId}/resumeJWTPause stops charging without canceling. Resume restarts charges; the next billing date moves forward by the pause duration.
Update payment method
/banking/merchant/subscriptions/{subscriptionId}/payment-methodJWT{ "paymentMethodId": "pm_stripe_new" }
A self-service "update card" page in your app calls this endpoint after re-tokenising the new card via Stripe Elements.
Billing cycle
Each subscription has a billing job that fires at currentPeriodEnd:
- 1
Charge attempt
The platform asks the provider to charge the saved payment method for
plan.amount. - 2
Success path
Charge succeeds → subscription stays
active,currentPeriodStartadvances,currentPeriodEndrolls forward byinterval × intervalCount. Journal entry posts to the merchant's account. - 3
Failure path
Charge fails → subscription moves to
past_due. Dunning kicks in.
The schedule itself runs as a cron-style job. Configure it via Automation or your own scheduler hitting an internal billing endpoint.
Dunning
When a charge fails, the platform retries on a configurable schedule before giving up. Default retry pattern:
| Attempt | Timing |
|---|---|
| 1 | At currentPeriodEnd (initial attempt) |
| 2 | +3 days |
| 3 | +7 days |
| 4 | +14 days |
Between retries, the subscription is past_due. After the final failed retry, the subscription transitions to canceled and access should be revoked by your application.
Each retry attempt typically pairs with a customer-facing email — "Your card failed; update your payment method." Configure these via the Broadcast module and trigger them from Automation when a subscription.payment_failed event fires.
Pre-cancellation grace
cancelAtPeriodEnd: true is the most customer-friendly cancel — the user pays for the current period, retains access, and isn't billed again. Surface this prominently in your cancel flow; it dramatically reduces churn complaints.
Trial handling
Plans with trialDays > 0 create subscriptions in trialing state. No charge is attempted until trialEnd. To convert a trial early, call:
/banking/merchant/subscriptions/{subscriptionId}/end-trialJWTThe subscription jumps to active and the first charge runs immediately. To extend a trial, update the subscription record's trialEnd to a later date.
Stats
/banking/merchant/subscriptions/statsJWTReturns MRR, ARR, subscriber count by plan, churn rate for the trailing 30 days. Useful for SaaS dashboards.
What the subscriptions service doesn't do
- No proration computation — if you change a plan mid-cycle, you compute the proration on your side and pass it to the provider.
- No quantity-based plans — each subscription is one unit of one plan. For per-seat or metered billing, run separate subscriptions or use Stripe metered billing directly.
- No coupon engine — discounts are applied at the provider level. Pass discount metadata through and let the gateway handle the math.