Storefront
A site can be the workspace’s store: its pages read the catalog from Commerce, and its checkout opens Stripe Checkout on the workspace’s connected Stripe account. Turn the storefront on in Commerce › Settings, and connect Stripe in Settings › Integrations › Stripe to take card payments.
Money is always an integer in the store currency’s minor unit: 1900 is $19.00.
Page data
Section titled “Page data”POST /_platform/store/page-data answers a page’s data from a copy of the catalog that Digize rebuilds a few seconds after anything in it changes. A traffic spike reads only that copy, never the workspace’s records, so it can’t slow anything down. The body is one of:
| Query | Answers |
|---|---|
{ "page": "home" } |
store, collections, and the first 24 products as cards |
{ "page": "product", "slug": "cold-brew" } |
store, the whole product with its variants, and the collections it’s in |
{ "page": "collection", "slug": "single-origins", "cursor"?: "24" } |
store, the collection, its products 24 at a time, total and next |
{ "page": "search", "q": "ethiopia", "cursor"?: "24" } |
store, q, matching products 24 at a time, total and next |
A product’s slug is its URL segment (/products/cold-brew), and a collection’s is its handle. Search finds products whose name, category, vendor or tags have every word of q. next is the cursor for the next page, or null on the last. A slug the store doesn’t have answers 404 not_found, as does a store that has never been published.
// The parts of a product page this example reads.interface ProductPageData { page: "product"; store: { name: string; currency: string }; product: { name: string; slug: string; priceMin: number; available: boolean };}
async function pageData(origin: string, query: unknown): Promise<ProductPageData> { const response = await fetch(`${origin}/_platform/store/page-data`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(query), }); if (!response.ok) { const { error } = (await response.json()) as { error: { message: string } }; throw new Error(error.message); } return (await response.json()) as ProductPageData;}
export default { async fetch(request: Request): Promise<Response> { const url = new URL(request.url); const [, kind, slug] = url.pathname.split("/"); if (kind === "products" && slug) { const data = await pageData(url.origin, { page: "product", slug }); const price = data.product.priceMin / 100; return new Response(`${data.product.name}: ${price} ${data.store.currency}`); } return new Response("Not found", { status: 404 }); },};store has the store’s name, currency, tagline, brand color, logo, contact details, whether it’s open and whether checkout works now, its shipping rates, its tax setting and its policies. A product has its options, images (each with thumb, card and full URLs), variants with their prices, availability and maxQuantity, and its SEO title and description. Stock in the copy can trail the live count by a few seconds; checkout checks it again.
Checkout
Section titled “Checkout”POST /_platform/store/checkout prices the cart from the live catalog, holds its stock for 30 minutes, and opens a Stripe Checkout Session:
{ "lines": [{ "variantId": "var_01JD7QM5Q7S9U1W3Y5A7C9E1GH", "quantity": 2 }], "email": "maya@example.com", "discountCode": "WELCOME10", "successUrl": "https://brightwave.yourfreesite.com/checkout/done", "cancelUrl": "https://brightwave.yourfreesite.com/cart", "attribution": { "utm_source": "newsletter" }}It answers { "checkoutId": "chk_…", "url": "https://checkout.stripe.com/…" }; send the customer to url with a 303. Stripe sends them back to successUrl with ?checkout=<checkoutId> added, or to cancelUrl.
linesis required: up to 50 different items and 99 of one item.successUrlandcancelUrlare required, usehttps, and must be on the site’s own hostname.email,discountCodeandattribution(UTM tags and click ids, for the order’s attribution and ad conversions) can benullor left out.
What can go wrong: 400 invalid for the cart or the return addresses, with details.fields; 409 conflict when an item isn’t for sale any more or has too few left (details.available says how many), or the same checkout is still starting; 503 unavailable when the store is closed, isn’t shipping, or can’t take card payments yet; and 429 past 120 checkouts a minute for the site.
When the customer pays, the order is made in Commerce › Orders, the stock is sold, the customer is added to the CRM, and a receipt goes out once the workspace has a sending domain (Sites › Email sending). An unpaid checkout’s stock goes back on sale after 30 minutes.
More fields
Section titled “More fields”A checkout also takes these, on the platform path or at POST https://digize.com/api/store/checkout (Calling the App Worker):
| Field | |
|---|---|
giftCardCode |
A gift card to pay with first. When it covers everything, the order is placed at once and url is the successUrl. |
shippingRateId |
One of the store’s shipping rates; the first one when left out. |
shippingAddress |
{ name, line1, line2?, city, region?, postalCode, country }. Stripe asks for it when it’s left out, so it’s only needed when a gift card pays for everything. |
name, phone |
The customer’s. |
idempotencyKey |
For a day, the same key and cart answer the first checkout instead of holding the stock twice. A new UUID per checkout. At digize.com it can be the Idempotency-Key header instead. |
Checkout status
Section titled “Checkout status”POST /_platform/store/checkout-status with { "checkoutId": "chk_…" } answers how it went, for the page Stripe sends the customer back to:
{ "checkoutId": "chk_01JD7QZ8A0C2E4G6J8L0N2Q4S6", "status": "completed", "orderNumber": 1042, "total": 4850, "currency": "USD" }status is open while it’s waiting to be paid (a payment can take a few seconds to arrive after the redirect), completed once the order is placed, with its orderNumber, or expired. It’s limited to 120 a minute for the site.