Subscriptions
Charge a saved card on a schedule: plans, trials, coupons, proration, dunning and a hosted customer portal, all handled by Orchestrapay. This page explains every option; the Subscriptions API page has the endpoint reference.
How it works
Plan (pricing template) Subscription (customer instance)
amount / interval / trial price snapshot (grandfathered)
dunning / entitlements ──► card on file (payment method)
billing cycle + invoices
- You create a plan: the price, billing cadence, optional trial, dunning policy and entitlements.
- A customer subscribes with a card. The first cycle is paid on-session; every renewal is charged automatically off-session (a merchant-initiated transaction on the saved card).
- Each billing attempt writes an invoice (paid or failed, with the full subtotal / discount / tax / credit breakdown), sends the customer a receipt, and emits webhooks.
There are three ways to start a subscription:
- Hosted subscribe page (zero integration): create a checkout session and share the URL. The customer picks up the plan, enters their card and the subscription auto-creates.
- API / SDK: collect a card via the payment form with
save_on_file, thenorchestrapay.subscriptions.create({ plan_uuid, payment_method }). - Dashboard: create plans and subscriptions manually under Subscriptions.
Pricing options
- Amount and currency — integer minor units (cents) with a lowercase
3-letter currency. A $5.00 monthly plan is
amount: 500, currency: "usd". - Interval —
daily,weekly,monthlyoryearly, timesinterval_count(e.g.interval: "weekly", interval_count: 2bills every two weeks). - Grandfathering — the subscription snapshots the plan's price at creation. Editing or archiving a plan never changes what existing subscribers pay; only an explicit plan change does.
Pricing models
Every plan has a pricing_model that decides how amount and quantity combine
into the cycle price:
| Model | Cycle price | Use case |
|---|---|---|
flat (default) | amount | one price, quantity ignored |
per_unit | amount × quantity | per-seat pricing (the hosted page shows a seat picker) |
tiered | tier ladder over quantity | volume discounts |
metered | tier/unit price over reported usage | pay-as-you-go |
Tiered plans define a tiers ladder ([{ up_to: 10, unit_amount: 500 }, { up_to: null, unit_amount: 300 }]) with tiers_mode:
graduated— each tier bills its own units (first 10 at $5, the rest at $3)volume— the tier the total quantity lands in prices ALL units
Metered billing: report usage as it happens
(subscriptions.recordUsage); at each renewal the billed quantity is the
period total (increments summed, a set overrides). Metered plans collect
nothing at subscribe time — the card is saved with a no-charge setup.
Quantities are set at subscribe (quantity) and changed later with the
same proration rules as plan changes.
Multi-currency: give a plan currency_options ({ "eur": 450 }) and the
hosted subscribe page offers a currency picker; the chosen amount snapshots
onto the subscription.
Products group the price variants of one offering (Stripe's
Product → Price): create with orchestrapay.products.create, then pass
product_uuid when creating plans.
Add-ons (multiple line items)
Attach extra plans to a live subscription as add-on lines billed on the same
cycle: subscriptions.addItem(sub, { plan, quantity }). Attaching charges
the prorated remainder of the period; removing credits the unused time to the
customer balance. Invoices carry the full per-line breakdown in lines.
Scheduled changes
Queue future-dated plan/quantity changes with
subscriptions.schedule(sub, { effective_at, plan, quantity }) — they land
at the first billing run at or after the date, with no proration.
Trials
Three trial shapes, configured on the plan:
| Configuration | Behavior |
|---|---|
trial_days: 14 | 14 days free, then full price. Card saved upfront with no charge (a setup, not a payment). |
trial_days: 14, trial_amount: 100 | 14 days at a $1.00 intro price, then full price. |
trial_cycles: 3, trial_amount: 100 | First 3 billing cycles charged at $1.00, then full price. |
During a trial the subscription is trialing and gets full entitlements.
Coupons and promotion codes
A coupon is the discount (percent_off or amount_off) with a duration:
once— first invoice onlyrepeating+duration_in_months— the next N invoicesforever— every invoice
A promotion code is the customer-facing string (e.g. WELCOME20) that
redeems a coupon, with optional expiry and redemption caps. Customers enter it
on the hosted subscribe page; the discount is snapshotted onto the
subscription and burns down cycle by cycle.
Taxes
Attach a manual tax rate (percentage, inclusive or exclusive, a display
label like "VAT") when creating a subscription. Exclusive tax is added on top
of each invoice; inclusive tax is informational. Tax appears as its own line
on invoices and receipts.
Upgrades, downgrades and proration
Changing plans mid-cycle follows Stripe's proration model. The billing date never moves; the remaining time is settled fairly:
- Upgrade (
proration_behavior: "create_prorations", the default) — the unused time on the old price becomes a credit, the remaining time on the new price is charged, and the difference is collected now. Access changes immediately. - Downgrade — the difference becomes a customer balance credit, automatically consumed by future invoices (an invoice fully covered by credit charges the card nothing).
proration_behavior: "none"— swap the price with no settlement.- Period-end switch (
pending_plan_uuid) — no proration; the new plan starts at the next renewal.
Preview any change first with the upcoming invoice endpoint: it returns the exact charge-now amount or balance credit.
Failed payments and dunning
When a renewal charge fails the subscription goes past_due and the plan's
dunning schedule takes over (default [1, 3, 7]: retry after 1, 3 and 7
days):
- The customer gets a payment-failed email with a link to the hosted invoice page, where they can pay immediately or enter a new card (no login needed — the link itself authorizes it).
- Each scheduled retry charges the card again automatically. Any successful
payment (retry, hosted page, or card update) resets the subscription to
active. - During the plan's
grace_period_days(default 7) entitlements stay on; past it they turn off while the subscription remainspast_due. - When every retry is exhausted the subscription is canceled and the customer is notified.
Bank authentication (SCA / 3DS) on renewals
When an issuer demands authentication for an off-session charge, the invoice
parks as requires_action (with the bank's requires_action_url), the
customer gets a "confirm your payment" email, and the hosted invoice page
walks them through confirming with their bank and completing the payment.
The retry ladder keeps running as a fallback.
Cancellation, pause and reactivation
- Cancel at period end (default) — access continues until the paid period ends. Until then the customer can reactivate with one click.
- Cancel immediately — access ends now.
- Pause / resume — billing stops while paused (entitlements off); resuming bills immediately and starts a fresh period.
- A cancellation reason can be captured for retention analytics, and the portal offers pause-instead as a retention path.
Entitlements
Plans carry a features map (boolean flags or numeric limits, e.g.
{"api_access": true, "seats": 5}); subscriptions can override per customer
(feature_overrides, for enterprise deals). Check them at request time:
GET /subscriptions/entitlements/check?customer_id=<cus_ or your external_id>&feature=api_access
Status drives access automatically: active and trialing allow, past_due
allows during grace only, paused and canceled deny.
Customer portal
Every tenant gets a hosted, brand-styled portal at
/portal/<tenant> where customers sign in with an email code (no password)
and can:
- see their subscriptions, next billing date and exact upcoming charge
- switch plans with a proration preview before confirming
- update their card (saved with no charge)
- pause, resume, reactivate or cancel
- browse their full billing history
Credit notes and refunds
Issue post-invoice adjustments with orchestrapay.creditNotes.create({ invoice, amount, reason }) — the amount is credited to the customer
balance and consumed by future invoices (a fully covered renewal charges
the card nothing). Card refunds go through the
Refunds API against the invoice's
transaction_intent.
Invoice PDFs
Every invoice renders as a PDF: merchants via
GET /v202607/invoices/:id/pdf, customers via the download link on the
hosted invoice page.
Login-free portal links
orchestrapay.billingPortal.createSession({ customer }) mints a 15-minute
login-free portal URL — drop it behind a "Manage subscription" button in your
app so customers skip the email code.
Receipts and emails
Customers automatically receive branded emails for: sign-in codes, payment receipts (with the full amount breakdown), failed payments (with the pay-now link) and final cancellation.
Webhooks
Pass webhook_urls when creating a subscription to receive Stripe-style
envelope events (id / type / created_at / data):
| Event | When |
|---|---|
subscription.created | Subscription created |
subscription.updated | Plan change, pause/resume, reactivate, scheduled cancel |
subscription.canceled | Immediate cancel, period-end cancel executing, or dunning exhausted |
invoice.paid | A billing attempt succeeded |
invoice.payment_failed | A billing attempt failed (dunning is running) |
SDK
import Orchestrapay from '@orchestrapay/sdk';
const orchestrapay = new Orchestrapay('orch_sk_live_...');
const plan = await orchestrapay.plans.create({
name: 'Pro Monthly', amount: 500, currency: 'usd', interval: 'monthly',
});
// Zero-integration: share a hosted subscribe link
const session = await orchestrapay.checkoutSessions.create({ plan: plan.id });
// session.url -> https://pay.orchestrapay.com/subscribe/<tenant>/<plan>
// Or create directly on a saved card
const sub = await orchestrapay.subscriptions.create({
plan_uuid: plan.id,
payment_method: 'pm_...', // from a save-on-file payment
});
// Prorated upgrade with preview
const preview = await orchestrapay.subscriptions.upcoming(sub.id, { plan: 'plan_...' });
await orchestrapay.subscriptions.update(sub.id, {
plan_uuid: 'plan_...', proration_behavior: 'create_prorations',
});
See the Subscriptions API reference for every endpoint and field.