Subscriptions and rentals both deviate from one-shot product orders, but in opposite directions. A subscription charges the customer on a recurring cadence and ships the product (or grants access) each cycle. A rental charges once for time-bound use of a physical item and tracks pickup, return, and deposits. They live in the same checkout but follow separate state machines after.
Subscriptions
Subscriptions in commerce are distinct from org-level subscription plans (the platform's billing — see org management). These are merchant-defined recurring products.
Read and update
/storefront/subscriptions/get/:author/:subscriptionid?No auth/storefront/update-subscriptionJWT/storefront/stripe/subscription-sessionNo authget returns one or all subscriptions belonging to an author (customer ID or guest ID). update-subscription changes plan, quantity, or pause/resume state. stripe/subscription-session creates a Stripe Checkout session for a new subscription — the redirect flow.
const session = await fetch('/api/storefront/stripe/subscription-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json', orgid: ORG_ID },
body: JSON.stringify({
cart: cartObject,
customerId: customerId,
successUrl: 'https://shop.example/sub-success',
cancelUrl: 'https://shop.example/cart',
}),
}).then(r => r.json());
window.location = session.url;
After Stripe completes the checkout, webhooks (handled by the Stripe vendor connect) write the subscription record and emit subscription.created. The customer's authenticated subscriptions list comes from the data layer (/data/subscription?where[customerId]=...).
Subscription lifecycle
Each subscription has:
status:active,paused,cancelled,past-due,expiredcurrentPeriodStart/currentPeriodEndnextBillingDatenextShipmentDate(for product subs)cancelAtPeriodEnd: boolean
Renewals happen on the Stripe webhook invoice.payment_succeeded. The platform writes a new order for the cycle and ships it via the inventory + shipping pipeline. Failed payments move the subscription to past-due; the dunning emails come from the marketing module.
Pause and skip
update-subscription accepts action: "pause" | "resume" | "skip-cycle" | "change-plan" | "change-quantity". Skipping a cycle moves nextBillingDate forward by one period without cancelling. Pausing stops billing indefinitely.
Rentals
Rentals share the cart with products via rentalItems[] (see Cart). Each rental item carries startDate, endDate, rentalPeriod, and a deposit. Checkout splits the cart: products go to a regular order, rentals become rental records.
Configuration and catalogue
/storefront/rental/configNo auth/storefront/rental/itemsNo auth/storefront/rental/items/:skuNo auth/storefront/rental/items/:sku/availabilityNo auth/storefront/rental/items/:sku/priceNo authconfig returns the rental settings (deposit policy, late fee schedule, grace period). availability returns booked windows for a SKU so the customer's date picker can grey out unavailable times. price calculates total cost given a SKU and date range.
Booking and lookup
/storefront/rentalNo auth/storefront/rentalJWT/storefront/rental/:rentalIdJWT/storefront/rental/status/overdueJWT/storefront/rental/status/due-todayJWTThe first POST creates a rental record without going through the cart split — used for direct booking flows. The overdue and due-today lists power the operations dashboard.
Rental lifecycle
The state machine: pending → confirmed → ready → checked-out (or picked-up/delivered) → active → returned (or dropped-off) → completed. Each transition has its own endpoint:
/storefront/rental/:rentalId/confirmJWT/storefront/rental/:rentalId/readyJWT/storefront/rental/:rentalId/checkoutJWT/storefront/rental/:rentalId/picked-upJWT/storefront/rental/:rentalId/deliveredJWT/storefront/rental/:rentalId/activeJWT/storefront/rental/:rentalId/checkinJWT/storefront/rental/:rentalId/returnedJWT/storefront/rental/:rentalId/dropped-offJWT/storefront/rental/:rentalId/overdueJWT/storefront/rental/:rentalId/completeJWT/storefront/rental/:rentalId/cancelJWT/storefront/rental/:rentalId/notesJWTDifferent fulfillment models — counter pickup, delivery, drop-off return, mail-back — pick different transitions. The state machine doesn't enforce a single path; it enforces only that you can't skip backwards.
Deposits
/storefront/rental/:rentalId/deposit/holdJWT/storefront/rental/:rentalId/deposit/refundJWT/storefront/rental/:rentalId/deposit/deductionJWTDeposits are held against a card (Stripe authorization), not charged. On clean return, refund releases the hold. Damage or missing items use deduction to capture some of the held amount before releasing the rest.
Payments
/storefront/rental/:rentalId/paymentJWTFor multi-stage rentals (long-term, partial-payment models) — record an additional payment against the rental.
Restrictions
/storefront/rental/items/:sku/validate-restrictionsNo authSome rentals require licences, age verification, or insurance. validate-restrictions accepts the customer's documents and returns whether they're allowed to rent the SKU.
Inventory reservations work the same for rentals as for sales — the SKU is reserved for the booked window. The reservation auto-releases on cancel or complete. See Inventory.
Mixed checkout
A cart with both productItems and rentalItems is checked out via POST /storefront/checkout-mixed. The platform creates one order for products and one rental record (or several) for rental items, both linked back to a single payment intent so the customer pays once.