Recipe: subscribe & save
Goal: let a shopper opt into recurring delivery at checkout, so the order both places now and creates a subscription for future renewals.
Audience: storefront (server-side, via the storefront SDK).
Subscriptions on Flightdeck are a real recurring-billing engine — a schedule, an exactly-once renewal sweep, and a dunning ladder driven off a vaulted merchant-initiated credential. This recipe covers only the storefront opt-in; the engine and its lifecycle routes are in the subscriptions reference.
Prerequisites
- The store’s manifest must have subscribe-and-save enabled:
behavior.subscriptions.enabled(defaultfalse). Interval discounts live inbehavior.subscriptions.discounts(interval-days to percent off, e.g."30","60"). - Checkout is one-shot: you submit the cart and card together. There is no separate “create subscription” call from the storefront — it rides the checkout.
Step 1 — render the auto-renewal disclosure before the opt-in
This is not optional and not cosmetic. A subscription created from the
storefront requires recorded consent evidence, enforced at the route: a
checkout with subscribe: true but no consent block is refused with
400 subscription_consent_required.
Render the auto-renewal disclosure (its terms: cadence, price, how to cancel) before the opt-in control, and capture:
disclosureRef— the disclosure version the shopper saw (e.g.arl-disclosure-v1).disclosureSha256— a SHA-256 hex of the exact rendered disclosure text.ipHash(optional) — a SHA-256 hash of the shopper’s IP.userAgent(optional) — the shopper’s user agent.
The hosted checkout sends this automatically. A custom frontend must supply it.
Step 2 — submit the checkout with subscribe and the consent block
// Server-side Route Handler — card fields never touch the browser.
const result = await store.checkout.submit(
{
customer_ref: "guest:shopper@example.com",
items: [{ variant_id: "var_123", quantity: 1 }],
card: { ccnumber: "4111111111111111", ccexp: "12/28", cvv: "123" },
email: "shopper@example.com", // the order-confirmation + ARL-terms address
subscribe: true, // opt into subscribe-and-save (if eligible)
subscriptionConsent: {
disclosureRef: "arl-disclosure-v1",
disclosureSha256: "…", // SHA-256 hex of the rendered disclosure
ipHash: "…", // optional
userAgent: "…", // optional
},
},
{ idempotencyKey: `cart-${cartId}:attempt-1` }, // required Idempotency-Key
);
subscriptionConsentis enforced at the route but is not yet part of the generated request types — include it alongside the typed fields. The shopper’s typedcustomer_contact_emailand is where the confirmation (and the auto-renewal terms acknowledgment) is delivered.
Step 3 — read the outcome
On a placed order the response carries a subscriptions array alongside the
usual checkout outcome fields
(order, card_charged_cents, replayed, …). Handle every branch the same way
you would a normal checkout:
- Placed (201) — the order placed and the subscription was created.
- Replayed (200) — the same idempotency key returns the original order with
replayed: true. Treat as success; do not re-submit. 400 subscription_consent_required— you did not send a valid consent block. This is a real error, not a decline; fix the flow, do not retry blind.- Declined / error — the SDK throws
FlightdeckError; show a decline state, do not auto-retry (double-charge risk).
What the platform guarantees after opt-in
You do not manage renewals — the engine does, with an
exactly-once renewal invariant (a
UNIQUE (subscription, period) invoice, the deterministic key
sub-renewal:{sub_id}:{period}, and next_bill_at advancing only inside the
settle transaction). Note for compliance:
- Every store sends a pre-billing renewal reminder. The cadence
(
renewalReminderDaysBeforeBilling) is per-store (default 7) with an enforced floor of 3 — reminders cannot be turned off, and a marketing unsubscribe never blocks them (they are transactional). - The consent evidence you captured is persisted on the subscription row.
Shopper self-serve
Give shoppers a way to manage what they signed up for. When
behavior.accounts.subscriptionSelfServe is on (default true), the
account page can list and cancel/pause/resume a shopper’s own subscriptions
through the shopper-scoped routes (see
subscriptions reference).
A self-serve cancel is immediate and sends a confirmation; a resume recomputes
next_bill_at forward and never back-bills.
Related
- Checkout — the full one-shot flow, totals, and fraud floor.
- Subscriptions reference — the engine, operator, and merchant routes.
- Money invariants — deterministic idempotency and exactly-once billing.