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.
Page data
Section titled “Page data”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.
Checkout
Section titled “Checkout”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. |
Sending it again safely
Section titled “Sending it again safely”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 outconst 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.
What can go wrong
Section titled “What can go wrong”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. |
Checkout status
Section titled “Checkout status”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.
Reviews
Section titled “Reviews”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.
Leaving a review
Section titled “Leaving a review”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.