Documentation

Subscriptions and recurring billing

Plans, subscriptions, dunning, and trial management on top of merchant checkout.

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.

FieldMeaning
planIdAuto-generated plan_<random>.
nameDisplay name.
amountPrice per period.
currencyISO 4217. Defaults to USD.
intervalday / week / month / year.
intervalCountMultiplier — interval: month, intervalCount: 3 = quarterly.
trialDaysOptional free-trial length.
featuresFree-text list, surfaced on checkout pages.
metadataFree-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

POST/banking/merchant/subscriptions/plansJWT
{
  "name": "Pro Monthly",
  "amount": 29.99,
  "currency": "USD",
  "interval": "month",
  "intervalCount": 1,
  "trialDays": 14,
  "features": ["Unlimited projects", "Priority support"]
}

List

GET/banking/merchant/subscriptions/plansJWT

Filter with status, page, pageSize.

Get / update

GET/banking/merchant/subscriptions/plans/{planId}JWT
PUT/banking/merchant/subscriptions/plans/{planId}JWT

Plan 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
StatusMeaning
trialingIn free trial; no charges yet.
activeCurrently billed.
past_dueLast charge failed; in dunning window.
canceledTerminal. Either by customer request or after exhausted dunning.
pausedTemporarily halted; no charges, period clock paused.

Create

POST/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

GET/banking/merchant/subscriptions/{subscriptionId}JWT

Returns the subscription with currentPeriodStart, currentPeriodEnd, trialStart, trialEnd, and any cancellation flags.

Cancel

POST/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

POST/banking/merchant/subscriptions/{subscriptionId}/pauseJWT
POST/banking/merchant/subscriptions/{subscriptionId}/resumeJWT

Pause stops charging without canceling. Resume restarts charges; the next billing date moves forward by the pause duration.

Update payment method

PUT/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. 1

    Charge attempt

    The platform asks the provider to charge the saved payment method for plan.amount.

  2. 2

    Success path

    Charge succeeds → subscription stays active, currentPeriodStart advances, currentPeriodEnd rolls forward by interval × intervalCount. Journal entry posts to the merchant's account.

  3. 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:

AttemptTiming
1At 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:

POST/banking/merchant/subscriptions/{subscriptionId}/end-trialJWT

The 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

GET/banking/merchant/subscriptions/statsJWT

Returns 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.