Skip to content
Browse documentation

Products & subscriptions

The catalog is the heart of a store. NanoSite supports two kinds of products — standard (one-time purchase) and subscription (recurring billing) — plus variants, options, multi-currency pricing, inventory, and collections.

Product types

A product's type is one of:

Type Value Billing
Standard standard One-time purchase (default).
Subscription subscription Recurring billing on an interval.

The type controls how pricing works and how checkout behaves.

Standard products

A normal one-time product. Prices carry no recurring interval. Checkout charges the shopper once.

Subscription products

A recurring product. Every price must declare a recurring interval:

  • recurringInterval — one of day, week, month, year.
  • recurringIntervalCount — how many intervals per billing cycle (1–52, default 1). For example month × 3 bills quarterly.

These map directly to Stripe's recurring price model. NanoSite enforces the invariant at create time: a subscription product must have a recurring interval on every price; a standard product must have none.

Subscription rules (current phase)

Subscriptions are backed by Stripe's hosted subscription checkout, which imposes some rules today:

  • Stripe only. A subscription can only be paid with the Stripe provider.
  • Standalone cart. A subscription must be purchased on its own — you can't mix a subscription and one-time items in the same cart.
  • Hosted redirect. Subscription checkout returns a redirectUrl to Stripe's hosted Checkout (mode=subscription); you must supply successUrl and cancelUrl.
  • Webhook required. The store's Stripe webhook signing secret must be configured — renewals and status changes arrive by webhook.
  • Discounts, shipping, and tax are skipped for subscription checkouts in this phase (Stripe bills the recurring price directly).

Subscription records track the lifecycle with a normalized status — active, trialing, past_due, unpaid, incomplete, or canceled — kept current by Stripe webhooks. Stripe remains the billing source of truth. Subscriptions are readable via the admin API (useListAdminSubscriptions).

Prices & currency

Money is always stored as integer minor units — 1999 means $19.99 — and a product can carry one price per currency for true multi-currency pricing. Amounts never use floats, so there's no rounding drift.

Options & variants

  • Options are the choosable axes of a product — e.g. Size with values S/M/L, Color with Black/White. They're defined per product.
  • Variants are the concrete purchasable combinations (e.g. Medium / Black), each with its own sku, per-currency prices, and inventory.

This is exactly what apparel and fashion stores need: a T-shirt with Size and Color generates a variant per combination, each independently priced and stocked. High-value categories work the same way — a ring can offer a Material and ring Size, or a made-to-order piece can carry custom options. There's no practical limit that makes NanoSite "boutique-only".

Inventory model

Inventory follows a Shopify-style "track or don't" model:

  • Omit onHand → the variant is untracked: effectively unlimited, always in stock, never counts down, and holds no reservation.
  • Provide onHand → the variant is tracked: stock is reserved at checkout and decremented on sale. An explicit trackInventory flag always wins if you set it.
  • allowBackorder lets a tracked variant sell past zero.

Reservations are evaluated under a row lock, so concurrent checkouts can't oversell a tracked variant.

Collections

Collections group products (e.g. New arrivals, Sale). A product joins collections via its collectionIds, and the storefront can list a collection together with its published products.

Querying products from the storefront

Use useListStoreProducts with filters — q (search over title/description/options/collections), collection, currency, minPrice/maxPrice, and sort (relevance, newest, title, price_asc, price_desc, rating). Fetch a single product with useGetStoreProduct (by slug).

See the SDK overview for how these hooks are configured.