Skip to content

Storefront

digize.store wraps the storefront’s platform paths: page data from the store’s catalog, checkout through Stripe, whether a checkout was paid, and the reviews widget. Page data and reviews are read from a copy Digize keeps at the edge, so a busy storefront never slows the workspace down.

Each storefront page has a method, and each answers that page’s typed data with the store itself:

const home = await digize.store.home();
// { page: "home", store, collections, products }
const { product, collections } = await digize.store.product("cold-brew");
// { page: "product", store, product, collections }
const shelf = await digize.store.collection("single-origins");
// { page: "collection", store, collection, products, total, next }
const results = await digize.store.search("ethiopia natural");
// { page: "search", store, q, products, total, next }

store.page(query) takes the query itself, { page: "home" }, { page: "product", slug }, { page: "collection", slug, cursor? } or { page: "search", q, cursor? }, and its answer’s type follows the page asked for (PageDataFor<Q>).

Collections and search come 24 products a page. next is the cursor for the page after, or null on the last:

let page = await digize.store.collection("single-origins");
const products = [...page.products];
while (page.next) {
page = await digize.store.collection("single-origins", page.next);
products.push(...page.products);
}

A product or collection that isn’t in the store throws not_found, and a store with nothing published yet throws not_found for every page. The shapes (StorefrontStore, StorefrontProduct, StorefrontProductCard, StorefrontCollection, StorefrontVariant, StorefrontImage) are exported from @digize/sdk. Prices are in the currency’s minor unit: 1900 is $19.00.

store.checkout(cart) prices the cart from the live catalog, holds its stock for 30 minutes, opens a Stripe Checkout Session, and answers where to send the customer:

const { checkoutId, url } = await digize.store.checkout({
lines: [{ variantId: "var_01JD7QM5Q7S9U1W3Y5A7C9E1GH", quantity: 2 }],
email: "maya@example.com",
discountCode: "WELCOME10",
successUrl: "https://shop.brightwave.com/checkout/done",
cancelUrl: "https://shop.brightwave.com/cart",
});

In a Worker, answer with Response.redirect(url, 303); in the browser, location.assign(url). Stripe sends the customer back to successUrl with ?checkout=<checkoutId> added, or to cancelUrl.

Field
lines Required: { variantId, quantity } for each item.
successUrl, cancelUrl Required: https addresses on the site’s own hostname.
email, discountCode Optional; sent as null when left out.
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 (store.shipping); 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.
attribution UTM tags and click ids for the order. In the browser, the ones in the page’s address are sent, with these added.
idempotencyKey See below.

Every checkout goes with an idempotency key: a new UUID unless you give one. For a day, the same key and cart answer the first checkout instead of holding the stock twice or, when a gift card pays for everything, placing a second order. The client sends a checkout once more by itself, with the same key, when it got no answer or a 502, 503 or 504. To retry it yourself, such as after the visitor clicks again, pass the key you used:

const idempotencyKey = crypto.randomUUID(); // one per cart the visitor checks out
const started = await digize.store.checkout({ ...cart, idempotencyKey });

The same key with a different cart throws invalid; one sent while the first is still starting throws conflict.

code When
invalid The cart or a return address isn’t valid; fields names which.
conflict 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.
unavailable The store is closed, isn’t shipping, or can’t take card payments yet.
rate_limited Over 120 checkouts a minute for the site.

On the page Stripe sends the customer back to, read the checkout from the address:

const checkoutId = new URL(request.url).searchParams.get("checkout");
if (checkoutId) {
const { status, orderNumber, total, currency } =
await digize.store.checkoutStatus(checkoutId);
}

status is open while it waits to be paid (a payment can take a few seconds to arrive after the redirect, so check again shortly), completed once the order is placed, with its orderNumber, or expired. Something that isn’t a checkout id (chk_ and 26 letters and digits) throws invalid, and one this site never started not_found.

store.reviews(query) reads the reviews widget’s reviews, the newest first, with the store’s average and count and the layout chosen in Reviews settings:

const { layout, average, count, reviews, next } = await digize.store.reviews({
productSlug: "cold-brew", // only this product's; leave it out for every review shown
limit: 6, // 12 unless it says, 50 at most
});
for (const review of reviews) {
console.log(review.rating, review.reviewer, review.body, review.reply?.body);
}

Pass next as cursor for the page after. Each review has its source (google, facebook or site), rating, title, body, the reviewer as a first name and initial, productSlug, createdAt and the team’s reply.

store.submitReview(review) keeps a visitor’s review for the team to approve; it isn’t shown until they do.

const { reviewId } = await digize.store.submitReview({
rating: 5,
name: "Maya Chen",
title: "Lovely beans",
body: "Smooth, sweet and fresh.",
email: "maya@example.com",
productSlug: "cold-brew",
turnstileToken,
});

rating and name are required; title, body, email and productSlug are optional. It needs a Turnstile token, as forms do, from the widget rendered with the site key forms.settings() answers (Turnstile). When the visitor came from a review request’s link, its ?review= parameter matches the review to the request: in the browser the client sends it as requestToken by itself, and in a Worker pass it from the address.