Documentation

Subscriptions and rentals

Recurring product subscriptions and time-based rentals — two distinct flows that share the cart.

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

GET/storefront/subscriptions/get/:author/:subscriptionid?No auth
POST/storefront/update-subscriptionJWT
POST/storefront/stripe/subscription-sessionNo auth

get 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, expired
  • currentPeriodStart / currentPeriodEnd
  • nextBillingDate
  • nextShipmentDate (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

GET/storefront/rental/configNo auth
GET/storefront/rental/itemsNo auth
GET/storefront/rental/items/:skuNo auth
GET/storefront/rental/items/:sku/availabilityNo auth
POST/storefront/rental/items/:sku/priceNo auth

config 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

POST/storefront/rentalNo auth
GET/storefront/rentalJWT
GET/storefront/rental/:rentalIdJWT
GET/storefront/rental/status/overdueJWT
GET/storefront/rental/status/due-todayJWT

The 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:

PUT/storefront/rental/:rentalId/confirmJWT
PUT/storefront/rental/:rentalId/readyJWT
PUT/storefront/rental/:rentalId/checkoutJWT
PUT/storefront/rental/:rentalId/picked-upJWT
PUT/storefront/rental/:rentalId/deliveredJWT
PUT/storefront/rental/:rentalId/activeJWT
PUT/storefront/rental/:rentalId/checkinJWT
PUT/storefront/rental/:rentalId/returnedJWT
PUT/storefront/rental/:rentalId/dropped-offJWT
PUT/storefront/rental/:rentalId/overdueJWT
PUT/storefront/rental/:rentalId/completeJWT
PUT/storefront/rental/:rentalId/cancelJWT
PUT/storefront/rental/:rentalId/notesJWT

Different 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

PUT/storefront/rental/:rentalId/deposit/holdJWT
PUT/storefront/rental/:rentalId/deposit/refundJWT
POST/storefront/rental/:rentalId/deposit/deductionJWT

Deposits 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

POST/storefront/rental/:rentalId/paymentJWT

For multi-stage rentals (long-term, partial-payment models) — record an additional payment against the rental.

Restrictions

POST/storefront/rental/items/:sku/validate-restrictionsNo auth

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