From e70853ef763b753f86fcf9413a1a0f039a41b562 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Wed, 5 Aug 2026 19:37:57 +0300 Subject: [PATCH 01/31] docs(ecom): migrate storefront cart/checkout references to Cart V2 Cart V1 + Checkout V1 are replaced by the unified Cart V2 API (both V1 APIs removed 2027-02-01). Updates the headless store recipe and the vibe-headless storefront reference to Cart V2: - currentCart -> currentCartV2; addToCurrentCart -> addLineItemsToCurrentCart (lineItems -> catalogItems) - REST paths /ecom/v1/carts/current/* -> /ecom/v2/carts/current/* - No checkout entity: drop createCheckoutFromCurrentCart; the cart id IS the checkout id, fed straight into the redirect session - Line-item read shapes: quantity->quantityInfo.confirmedQuantity, productName->name, price->pricing, image->attributes.image, availability.status->status (IN_STOCK/PARTIALLY_IN_STOCK/OUT_OF_STOCK/REMOVED_FROM_CATALOG) - update-line-items body now {lineItemId, quantity:{newQuantity}} Co-Authored-By: Claude Opus 4.8 --- .../inline-recipes/how-to-code-a-store.md | 41 +++++----- .../references/storefront/INSTRUCTIONS.md | 8 +- .../references/storefront/wix-store-cart.js | 75 ++++++++++--------- 3 files changed, 65 insertions(+), 59 deletions(-) diff --git a/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md b/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md index 3023f8c3c..939ab1da4 100644 --- a/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +++ b/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md @@ -24,7 +24,7 @@ A concise contract for writing the **frontend code** of a storefront against a C | Products (list, get, search, filter) | `@wix/stores` | `productsV3` | | Variants (to resolve `variantId`) | `@wix/stores` | `readOnlyVariantsV3` | | Categories | `@wix/stores` | `categories` | -| Cart (add / get / checkout) | `@wix/ecom` | `currentCart` | +| Cart (add / get / checkout) | `@wix/ecom` | `currentCartV2` | | Redirect to hosted checkout | `@wix/redirects` | `redirects` | **Never** import the V1 `products` or `collections` modules from `@wix/stores`. @@ -35,11 +35,11 @@ A concise contract for writing the **frontend code** of a storefront against a C ```js import { createClient, OAuthStrategy } from '@wix/sdk'; import { productsV3, readOnlyVariantsV3 } from '@wix/stores'; - import { currentCart } from '@wix/ecom'; + import { currentCartV2 } from '@wix/ecom'; import { redirects } from '@wix/redirects'; const client = createClient({ - modules: { productsV3, readOnlyVariantsV3, currentCart, redirects }, + modules: { productsV3, readOnlyVariantsV3, currentCartV2, redirects }, auth: OAuthStrategy({ clientId: /* the project's PUBLIC OAuth client id */ }), }); ``` @@ -49,7 +49,7 @@ A concise contract for writing the **frontend code** of a storefront against a C ## The shapes you read (field cheat-sheet) -The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to method-return wrappers (note `checkoutId`). +The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to request params (note the redirect session's `checkoutId`, which is just the cart's `_id`). ```jsonc // productsV3.queryProducts().…find() → result.items[] @@ -72,11 +72,12 @@ variant = { inventoryStatus: { inStock }, // variant-level stock (boolean) } -// currentCart.getCurrentCart() → { lineItems: [...] } -lineItem = { quantity, price: { amount }, image } // price is HERE (NOT actualPriceRange); image is wix:image:// too → resolve +// currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: wrapped in { cart } (V1 returned the cart directly) +lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity }, pricing: { unitPrice: { amount } }, attributes: { image } } +// price → pricing.unitPrice.amount (raw string, NO currency symbol in V2 — format it yourself); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve) -// currentCart.createCheckoutFromCurrentCart({ channelType }) → { checkoutId } // a STRING — NOT { checkout }, NOT _id -// redirects.createRedirectSession({ ecomCheckout: { checkoutId }, callbacks }) → { redirectSession: { fullUrl } } +// Cart V2 has NO checkout entity — the cart's _id IS the checkout id; there is no createCheckout call: +// redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks }) → { redirectSession: { fullUrl } } ``` --- @@ -94,7 +95,7 @@ Doc: · catalogReference contract: +**2 · Add it.** Doc: · catalogReference contract: ```js -await currentCart.addToCurrentCart({ - lineItems: [{ +await currentCartV2.addLineItemsToCurrentCart({ + catalogItems: [{ // Cart V2: `catalogItems`, NOT V1's `lineItems` quantity, catalogReference: { catalogItemId: product._id, // the product's _id (the `_id` rule above) @@ -170,19 +171,21 @@ await currentCart.addToCurrentCart({ ### Checkout -Create a checkout from the current cart, then redirect the buyer to the hosted checkout. -Docs: · +Cart V2 has no separate checkout entity — the cart's `_id` **is** the checkout id. Read the current cart, then hand its id to a redirect session, which carries the visitor/member session across to the hosted checkout on its own domain. +Doc: ```js -const checkout = await currentCart.createCheckoutFromCurrentCart({ channelType: currentCart.ChannelType.WEB }); +const { cart } = await currentCartV2.getCurrentCart(); // NOTE: wrapped in { cart } (V1 returned the cart directly) const session = await redirects.createRedirectSession({ - ecomCheckout: { checkoutId: checkout.checkoutId }, // checkout.checkoutId — NOT checkout._id + ecomCheckout: { checkoutId: cart._id }, // the cart's _id IS the checkout id — there is no createCheckout call callbacks: { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` }, }); window.location.href = session.redirectSession.fullUrl; // the hosted-checkout URL ``` -**⚠️ Return shapes are in the cheat-sheet** — `createCheckoutFromCurrentCart` gives **`checkout.checkoutId`** (a string), not `checkout._id`. Reading `checkout._id` (over-applying the `_id` rule) throws *"Cannot read properties of undefined (reading '_id')"* — the silent checkout crash. +**⚠️ There is no `createCheckoutFromCurrentCart` in Cart V2.** The V1 flow created a checkout entity and returned a `checkoutId`; V2 unifies cart + checkout, so you pass the cart's own `_id` straight into the redirect session's `ecomCheckout.checkoutId`. And `getCurrentCart()` now returns **`{ cart }`** (V1 returned the cart directly) — destructure it, or `cart` is `undefined` and `cart._id` throws *"Cannot read properties of undefined (reading '_id')"*. + +> **Simpler alternative (see PR note):** `cartV2.getCheckoutUrl(cartId)` returns a hosted-checkout URL directly, letting you drop `@wix/redirects`. It isn't used here because the redirect session is what carries the visitor/member session across domains; whether the plain URL preserves that for a headless storefront is unverified. **⚠️ CRITICAL: `origin` for `postFlowUrl`/`thankYouPageUrl` MUST be the `https://` published host — derive it from `window.location.origin`, NEVER `new URL(request.url).origin`.** The Headless redirect allowlist registers the site's **`https://`** host and treats **`http://` as a different, unlisted origin**. When the buyer returns from the hosted checkout (e.g. clicks "Continue Browsing"), the redirect goes through the allowlist — and an `http://` `postFlowUrl` **403s** with *"… isn't listed as an allowed redirect domain."* If you build the redirect session in a **server route** (`src/pages/api/*`), `new URL(request.url).origin` resolves to **`http://`** behind Wix's TLS-terminating proxy → guaranteed 403 on return. So **pass `window.location.origin` from the client** into the route (don't read the origin off the request), or force the scheme to `https`. Doc: . @@ -208,7 +211,7 @@ function imgSrc(mediaMain, w = 600, h = 600) { **Never hand-build a `static.wixstatic.com/.../v1/fit/...` URL** either — the format is easy to get wrong and the image then **403s**. Only `wix:image://` values need resolving; an already-absolute `https://` URL goes straight into ``. Doc: -**This applies to cart line-item images too, not just product reads.** A cart `lineItem.image` is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before ``. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.) +**This applies to cart line-item images too, not just product reads.** A cart `lineItem.attributes.image` (Cart V2 nests it under `attributes`) is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before ``. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.) ### Rendering product descriptions @@ -230,7 +233,7 @@ Optional: render a `Product` schema.org JSON-LD `