From 08a23e5d74923947c40f74ffedc399e1c46a8eb5 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Wed, 5 Aug 2026 19:37:57 +0300 Subject: [PATCH 01/13] chore(commerce): migrate astro storefront to Cart V2 Migrates the astro/commerce template from @wix/ecom currentCart (Cart V1 + Checkout V1) to currentCartV2 (Cart V2). Both V1 APIs are removed 2027-02-01. - currentCart.addToCurrentCart -> currentCartV2.addLineItemsToCurrentCart (lineItems -> catalogItems) - getCurrentCart() now returns { cart } (destructured) - estimateCurrentCartTotals -> estimateCurrentCart; totals under summary.priceSummary - No checkout entity: feed the current cart's _id to createRedirectSession (cart id is the checkout id) instead of createCheckoutFromCurrentCart - Line-item read shapes moved (quantityInfo.confirmedQuantity, name, pricing, attributes.image); added a client-side money formatter since V2 line-item prices are raw ConvertedMoney (no formatted string) Co-Authored-By: Claude Opus 4.8 --- astro/commerce/README.md | 4 +- astro/commerce/src/components/AppIsland.jsx | 66 ++++++++++++++------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/astro/commerce/README.md b/astro/commerce/README.md index 71a43cdc..f9475f65 100644 --- a/astro/commerce/README.md +++ b/astro/commerce/README.md @@ -7,8 +7,8 @@ A minimal Astro + React storefront wireframe backed by Wix Stores and Wix eComme ## How it connects to Wix - **Catalog** — pages query products server-side with `@wix/stores` (`productsV3.queryProducts`). -- **Cart** — the React island uses `@wix/ecom` `currentCart` to add items, read the cart, and estimate totals. -- **Checkout** — `createCheckoutFromCurrentCart` plus `@wix/redirects` `createRedirectSession` sends the visitor to Wix Checkout. +- **Cart** — the React island uses `@wix/ecom` `currentCartV2` to add items, read the cart, and estimate totals. +- **Checkout** — Cart V2 has no separate checkout entity (the cart id is the checkout id), so `@wix/redirects` `createRedirectSession` is given the current cart's id to send the visitor to Wix Checkout. - **Members** — `@wix/members` reads the current member; login/logout go through the built-in `/api/auth/*` routes. - **Media** — product images are scaled with `media.getScaledToFillImageUrl` from `@wix/sdk`. diff --git a/astro/commerce/src/components/AppIsland.jsx b/astro/commerce/src/components/AppIsland.jsx index ea5a1ab6..de33d2d3 100644 --- a/astro/commerce/src/components/AppIsland.jsx +++ b/astro/commerce/src/components/AppIsland.jsx @@ -1,11 +1,34 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { currentCart } from '@wix/ecom'; +import { cartV2, currentCartV2 } from '@wix/ecom'; import { redirects } from '@wix/redirects'; import { media } from '@wix/sdk'; // Public app id of the Wix Stores catalog, used in ecom catalog references. const WIX_STORES_APP_ID = '215238eb-22a5-4c36-9e7b-e7c08025e04e'; +// Cart V2 line-item prices are raw decimal strings (ConvertedMoney: { amount, convertedAmount }), +// not preformatted display strings like Cart V1's MultiCurrencyPrice. Format them client-side +// from the amount + the cart's currency code. +function formatMoney(money, currencyCode) { + const value = money?.convertedAmount ?? money?.amount; + if (value == null) return ''; + const num = Number(value); + if (Number.isNaN(num)) return ''; + try { + return new Intl.NumberFormat(undefined, { style: 'currency', currency: currencyCode || 'USD' }).format(num); + } catch { + return `${value}`; + } +} + +function cartCurrency(cart) { + return cart?.customerInfo?.currencyCode ?? cart?.businessInfo?.currencyCode; +} + +function lineItemCount(cart) { + return cart?.lineItems?.reduce((s, i) => s + (i.quantityInfo?.confirmedQuantity ?? 0), 0) ?? 0; +} + function imgSrc(mediaMain, w = 600, h = 600) { const v = mediaMain?.image ?? mediaMain?.url ?? mediaMain; if (!v) return ''; @@ -63,8 +86,8 @@ function ProductModal({ product, onClose, onAddedToCart }) { if (!canAdd) return; setAdding(true); setError(''); try { - await currentCart.addToCurrentCart({ - lineItems: [{ quantity: 1, catalogReference: { + await currentCartV2.addLineItemsToCurrentCart({ + catalogItems: [{ quantity: 1, catalogReference: { catalogItemId: product._id, appId: WIX_STORES_APP_ID, ...(selectedVariant?._id && { options: { variantId: selectedVariant._id } }), @@ -119,12 +142,12 @@ function CartPanel({ onClose }) { useEffect(() => { Promise.all([ - currentCart.getCurrentCart(), - currentCart.estimateCurrentCartTotals().catch(() => null), + currentCartV2.getCurrentCart(), + currentCartV2.estimateCurrentCart().catch(() => null), ]) - .then(([c, estimate]) => { - setCart(c); - setPriceSummary(estimate?.priceSummary ?? null); + .then(([cartRes, estimate]) => { + setCart(cartRes?.cart ?? null); + setPriceSummary(estimate?.summary?.priceSummary ?? null); }) .catch(() => setCart(null)) .finally(() => setLoading(false)); @@ -133,19 +156,20 @@ function CartPanel({ onClose }) { async function handleCheckout() { setCheckingOut(true); try { - const checkout = await currentCart.createCheckoutFromCurrentCart({ channelType: 'WEB' }); + // Cart V2 has no separate checkout entity — the cart id is the checkout id. + // We still use a redirect session so the visitor/member session carries across + // to the Wix-hosted checkout on its own domain. + const { cart } = await currentCartV2.getCurrentCart(); const session = await redirects.createRedirectSession({ - ecomCheckout: { checkoutId: checkout.checkoutId }, + ecomCheckout: { checkoutId: cart._id }, callbacks: { postFlowUrl: window.location.origin + '/', thankYouPageUrl: window.location.origin + '/' }, }); window.location.href = session.redirectSession.fullUrl; } catch (e) { console.error(e); setCheckingOut(false); } } - const itemCount = cart?.lineItems?.reduce((s, i) => s + (i.quantity ?? 0), 0) ?? 0; - const subtotal = priceSummary?.subtotal?.formattedConvertedAmount - ?? priceSummary?.subtotal?.formattedAmount - ?? ''; + const itemCount = lineItemCount(cart); + const subtotal = priceSummary?.subtotal?.formattedAmount ?? ''; return (
@@ -158,14 +182,14 @@ function CartPanel({ onClose }) { {loading &&

Loading…

} {!loading && !cart?.lineItems?.length &&

Your cart is empty.

} {cart?.lineItems?.map((item, i) => { - const s = imgSrc(item.image); - const price = item.price?.formattedConvertedAmount ?? item.price?.formattedAmount; + const s = imgSrc(item.attributes?.image); + const price = formatMoney(item.pricing?.totalPrice, cartCurrency(cart)); return ( -
+
{s ? : }
-

{item.productName?.translated ?? item.productName}

-

Qty {item.quantity}

+

{item.name?.translated ?? item.name?.original}

+

Qty {item.quantityInfo?.confirmedQuantity}

{price &&

{price}

}
@@ -302,8 +326,8 @@ export default function AppIsland({ products = [], member = null, page = 'home' const refreshCart = useCallback(async () => { try { - const cart = await currentCart.getCurrentCart(); - setCartCount(cart?.lineItems?.reduce((s, i) => s + (i.quantity || 0), 0) ?? 0); + const { cart } = await currentCartV2.getCurrentCart(); + setCartCount(lineItemCount(cart)); } catch { setCartCount(0); } }, []); From 8598e453ea98a2b861b1a2ccfa4bc8226614567c Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 10:51:16 +0300 Subject: [PATCH 02/13] chore(templates): migrate remaining storefronts to Cart V2 Extends the Cart V2 migration to every remaining Cart V1 / Checkout V1 usage across the runnable templates (mappings verified against the ecom CartV1Proxy Scala V1<->V2 mappers): - nextjs/commerce: reshapeCart + full cart/checkout flow (price->pricing, quantity->quantityInfo.confirmedQuantity, productName->name, descriptionLines/url/image->attributes.*, currency->businessInfo/customerInfo) - nextjs/commerce-ticketing: all cart hooks/components + quick-buy (createCart catalogItems, overrideCheckoutUrl->customCheckoutUrl, updateCurrentCart(cart) shape) - react-native/mobile-ecommerce: add/update/remove/get + redirect checkout; coupon+note re-applied via addCouponToCurrentCart + updateCurrentCart note - nextjs/minimal-examples: store + installed-apps demos - astro/scheduler: booking checkout -> createCart + calculateCart + placeOrder - AGENTS.md: totals guidance -> estimateCurrentCart/calculateCart summary All redirect sessions keep @wix/redirects, fed the cart id (which is the checkout id in V2). Templates not run against a live site. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 2 +- astro/scheduler/src/utils/booking-service.ts | 42 ++++---- .../app/api/quick-buy/[productId]/route.ts | 14 +-- .../app/components/Cart/CartView.tsx | 26 +++-- .../app/components/CartBag/CartBag.tsx | 5 +- .../app/components/CartItem/CartItem.tsx | 31 +++--- .../components/Provider/ClientProvider.tsx | 5 +- .../app/hooks/useAddItemToCart.tsx | 32 +++--- .../commerce-ticketing/app/hooks/useCart.tsx | 13 ++- .../app/hooks/useRemoveItemFromCart.tsx | 4 +- .../app/hooks/useUpdateCart.tsx | 14 ++- .../app/hooks/useWixClientServer.ts | 6 +- nextjs/commerce/lib/wix/index.ts | 90 +++++++++-------- .../internal/utils/installed-apps.js | 14 +-- nextjs/minimal-examples/pages/store.js | 71 ++++++++------ .../authentication/wixClient.js | 5 +- .../screens/store/cart/CartScreen.js | 98 +++++++++++-------- .../screens/store/product/ProductScreen.js | 34 ++++--- 18 files changed, 292 insertions(+), 214 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ae10cccf..9b02c926 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ Never hardcode data the site owns. Every one of these was a real review finding: - **Services** (names, durations, prices): render from `services.queryServices()`, keyed by real `_id` — no hardcoded "free"/"premium" tiers. - **Forms**: render fields from `forms.getForm()` (labels, required flags, options, steps). Checkbox groups must render as checkboxes and submit **arrays** (`formData.getAll`), numbers as numbers, booleans as booleans. - **Products**: query with explicit fields (`CURRENCY`, `PLAIN_DESCRIPTION`, `VARIANT_OPTION_CHOICE_NAMES`); resolve variants from `variantsInfo` — never blindly add `variants[0]` for a product with options. -- **Prices and totals**: display the SDK's `formattedAmount`; cart totals come from `estimateCurrentCartTotals()`/`priceSummary`. Never prefix `$` or do `parseFloat` money math — stores run in EUR/GBP too. +- **Prices and totals**: display the SDK's `formattedAmount`; cart totals come from `currentCartV2.estimateCurrentCart()` / `cartV2.calculateCart()` → `summary.priceSummary`. Cart V2 line items carry only a raw `pricing.unitPrice.amount` (no `formattedAmount`), so format those from the cart's `currencyCode`. Never prefix `$` or do `parseFloat` money math — stores run in EUR/GBP too. - **Timezones**: query availability and format times in the visitor's timezone (`Intl.DateTimeFormat().resolvedOptions().timeZone`), never hardcoded UTC. - **Blog content**: render Ricos via `RicosViewer` from `@wix/astro-ricos` (with `renameKeysFromSDKRequestToRESTRequest`), not a hand-rolled node renderer. Source per-post SEO tags (title/description/canonical/OG) from `post.seoData.tags`, falling back to title/excerpt/cover. - Placeholder copy ("Business Name", template marketing text) is acceptable; placeholder *data* that shadows a Wix API is not. diff --git a/astro/scheduler/src/utils/booking-service.ts b/astro/scheduler/src/utils/booking-service.ts index f8216a6f..8b6985a0 100644 --- a/astro/scheduler/src/utils/booking-service.ts +++ b/astro/scheduler/src/utils/booking-service.ts @@ -1,5 +1,5 @@ import { services, availabilityCalendar, bookings } from "@wix/bookings"; -import { checkout } from "@wix/ecom"; +import { cartV2 } from "@wix/ecom"; import { redirects } from "@wix/redirects"; import { BOOKINGS_APP_ID, TIME_FORMAT } from "./constants"; @@ -157,8 +157,24 @@ export async function createBooking( }, }); - const createdCheckout = await checkout.createCheckout({ - lineItems: [ + // Cart V2 unifies cart + checkout: create a cart, calculate it to get the + // price-verification token, then place the order (replaces Checkout V1's + // createCheckout + createOrder). + const createdCart = await cartV2.createCart({ + cart: { + source: { channelType: "WEB" }, + customerInfo: { + email: bookingData.email, + }, + paymentInfo: { + billingContact: { + firstName: firstName, + lastName: lastName, + phone: bookingData.phone, + }, + }, + }, + catalogItems: [ { quantity: 1, catalogReference: { @@ -167,22 +183,14 @@ export async function createBooking( }, }, ], - channelType: checkout.ChannelType.WEB, - checkoutInfo: { - billingInfo: { - contactDetails: { - firstName: firstName, - lastName: lastName, - phone: bookingData.phone, - }, - }, - buyerInfo: { - email: bookingData.email, - }, - }, }); - await checkout.createOrder(createdCheckout._id!); + const cartId = createdCart._id!; + const calculated = await cartV2.calculateCart(cartId); + await cartV2.placeOrder(cartId, { + priceVerificationToken: + calculated.summary?.priceVerificationToken ?? undefined, + }); return booking; } catch (error) { diff --git a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts index 895294de..ecd024cf 100644 --- a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts +++ b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts @@ -1,7 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { getRequestUrl } from '@app/utils/server-utils'; import { getWixClient } from '@app/hooks/useWixClientServer'; -import { checkout as checkoutTypes } from '@wix/ecom'; import { STORES_APP_ID } from '@app/constants'; export async function GET( @@ -52,14 +51,17 @@ export async function GET( options: selectedOptions, }, }; - const checkout = await wixClient.ecomCheckout.createCheckout({ - lineItems: [item], - channelType: checkoutTypes.ChannelType.WEB, - overrideCheckoutUrl: `${baseUrl}api/redirect-to-checkout?checkoutId={checkoutId}`, + // Cart V2: create a fresh cart for this quick-buy. The cart id IS the checkout + // id, so we redirect straight from the created cart (no createCheckout call). + const cart = await wixClient.cartV2.createCart({ + cart: { + customCheckoutUrl: `${baseUrl}api/redirect-to-checkout?checkoutId={checkoutId}`, + }, + catalogItems: [item], }); const { redirectSession } = await wixClient.redirects.createRedirectSession({ - ecomCheckout: { checkoutId: checkout!._id! }, + ecomCheckout: { checkoutId: cart!._id! }, callbacks: { postFlowUrl: baseUrl, thankYouPageUrl: `${baseUrl}stores-success`, diff --git a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx index ac6941b8..9c6b52b1 100644 --- a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx +++ b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx @@ -6,23 +6,23 @@ import { useCart } from '@app/hooks/useCart'; import { useUI } from '@app/components/Provider/context'; import { useWixClient } from '@app/hooks/useWixClient'; import { Spinner } from 'flowbite-react'; -import { currentCart } from '@wix/ecom'; export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { const wixClient = useWixClient(); const { closeSidebar, openModalNotPremium } = useUI(); const { data, isLoading } = useCart(); const [redirecting, setRedirecting] = useState(false); + // Cart V2 currency lives on the cart, not on a top-level `currency` field. + const currencyCode = + data?.customerInfo?.currencyCode ?? data?.businessInfo?.currencyCode; const subTotal = formatPrice( data && { amount: data.lineItems?.reduce((acc, item) => { - return ( - acc + - Number.parseFloat(item.price?.amount ?? '0') * (item.quantity ?? 0) - ); + // V2 `totalPrice` is already the line total (unit x quantity). + return acc + Number(item.pricing?.totalPrice?.amount ?? 0); }, 0) ?? 0, - currencyCode: data.currency, + currencyCode, } ); @@ -30,13 +30,11 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { closeSidebar(); setRedirecting(true); try { - const checkout = - await wixClient.currentCart.createCheckoutFromCurrentCart({ - channelType: currentCart.ChannelType.WEB, - }); + // Cart V2 has no separate checkout entity: the cart id IS the checkout id. + const { cart } = await wixClient.currentCartV2.getCurrentCart(); const { redirectSession } = await wixClient.redirects.createRedirectSession({ - ecomCheckout: { checkoutId: checkout.checkoutId }, + ecomCheckout: { checkoutId: cart._id }, callbacks: { postFlowUrl: window.location.origin, thankYouPageUrl: `${window.location.origin}/stores-success`, @@ -48,7 +46,7 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { } } catch (e: any) { if ( - e.details.applicationError.code === + e?.details?.applicationError?.code === 'SITE_MUST_ACCEPT_PAYMENTS_TO_CREATE_CHECKOUT' ) { openModalNotPremium(); @@ -58,7 +56,7 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { }, [ closeSidebar, openModalNotPremium, - wixClient.currentCart, + wixClient.currentCartV2, wixClient.redirects, ]); @@ -110,7 +108,7 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { ))} diff --git a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx index 8c6de7a6..37dfa8af 100644 --- a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx +++ b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx @@ -1,14 +1,15 @@ 'use client'; import { useUI } from '@app/components/Provider/context'; import { useCart } from '@app/hooks/useCart'; -import { cart } from '@wix/ecom'; +import { cartV2 } from '@wix/ecom'; export const CartBag = () => { const { setSidebarView, toggleSidebar } = useUI(); const { data, isLoading } = useCart(); const itemsCount = !isLoading ? data?.lineItems?.reduce( - (count: number, item: cart.LineItem) => count + item.quantity!, + (count: number, item: cartV2.LineItem) => + count + (item.quantityInfo?.confirmedQuantity ?? 0), 0 ) : 0; diff --git a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx index f6f202fb..9b4572c9 100644 --- a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx +++ b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx @@ -2,7 +2,7 @@ import { ChangeEvent, useEffect, useState } from 'react'; import Link from 'next/link'; import { formatPrice } from '@app/utils/price-formatter'; -import { cart } from '@wix/ecom'; +import { cartV2 } from '@wix/ecom'; import { useUI } from '@app/components/Provider/context'; import { Quantity } from '@app/components/Quantity/Quantity'; import { useUpdateCart } from '@app/hooks/useUpdateCart'; @@ -15,19 +15,23 @@ export const CartItem = ({ hideButtons, ...rest }: { - item: cart.LineItem; + item: cartV2.LineItem; currencyCode: string; hideButtons?: boolean; }) => { const { closeSidebarIfPresent } = useUI(); const [removing, setRemoving] = useState(false); - const [quantity, setQuantity] = useState(item.quantity ?? 1); + const [quantity, setQuantity] = useState( + item.quantityInfo?.confirmedQuantity ?? 1 + ); const removeItem = useRemoveItemFromCart(); const updateCartMutation = useUpdateCart(); + // V2 pricing: `totalPrice` is already the line total (unit x quantity). + const lineTotal = Number(item.pricing?.totalPrice?.amount ?? 0); const price = formatPrice({ - amount: Number.parseFloat(item.price?.amount!) * item.quantity!, - baseAmount: Number.parseFloat(item.price?.amount!) * item.quantity!, + amount: lineTotal, + baseAmount: lineTotal, currencyCode, }); @@ -54,11 +58,12 @@ export const CartItem = ({ }; useEffect(() => { - if (item.quantity !== Number(quantity)) { - setQuantity(item.quantity!); + const confirmedQuantity = item.quantityInfo?.confirmedQuantity; + if (confirmedQuantity !== Number(quantity)) { + setQuantity(confirmedQuantity!); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item.quantity]); + }, [item.quantityInfo?.confirmedQuantity]); const slug = item.url?.split('/').pop() ?? ''; @@ -69,7 +74,11 @@ export const CartItem = ({ {slug ? (
- +
) : ( @@ -81,12 +90,12 @@ export const CartItem = ({ {slug ? ( - {item.productName?.translated} + {item.name?.translated} ) : ( - {item.productName?.translated} + {item.name?.translated} )}
diff --git a/nextjs/commerce-ticketing/app/components/Provider/ClientProvider.tsx b/nextjs/commerce-ticketing/app/components/Provider/ClientProvider.tsx index b9410653..8beabfdb 100644 --- a/nextjs/commerce-ticketing/app/components/Provider/ClientProvider.tsx +++ b/nextjs/commerce-ticketing/app/components/Provider/ClientProvider.tsx @@ -4,7 +4,7 @@ import { createContext, ReactNode } from 'react'; import { ManagedUIContext } from './context'; import { createClient, OAuthStrategy } from '@wix/sdk'; import { collections, productsV3 } from '@wix/stores'; -import { currentCart, backInStockNotifications } from '@wix/ecom'; +import { currentCartV2, cartV2, backInStockNotifications } from '@wix/ecom'; import { wixEventsV2 as wixEvents, orders as checkout } from '@wix/events'; import { redirects } from '@wix/redirects'; import Cookies from 'js-cookie'; @@ -17,7 +17,8 @@ const wixClient = createClient({ modules: { productsV3, collections, - currentCart, + currentCartV2, + cartV2, backInStockNotifications, wixEvents, checkout, diff --git a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx index 17b96766..cf81ce29 100644 --- a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx @@ -1,15 +1,25 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { WixClient } from '@app/components/Provider/ClientProvider'; -import { currentCart } from '@wix/ecom'; import { useWixClient } from './useWixClient'; +// NOTE: Cart V2 catalog-item shape passed to `addLineItemsToCurrentCart`. +// The precise exported V2 type (likely `currentCartV2.CatalogItem`) could not be +// verified without installed deps, so a structural type is used to keep this compiling. +type AddToCartCatalogItem = { + quantity?: number; + catalogReference: { + catalogItemId: string; + appId?: string; + options?: Record; + }; +}; + export const useAddItemToCart = () => { const wixClient = useWixClient(); const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: (item: currentCart.LineItem) => - addItemFromCart(wixClient, item), + mutationFn: (item: AddToCartCatalogItem) => addItemFromCart(wixClient, item), onSuccess: (data) => { queryClient.setQueryData(['cart'], data.cart); }, @@ -19,16 +29,16 @@ export const useAddItemToCart = () => { async function addItemFromCart( wixClient: WixClient, - item: currentCart.LineItem + item: AddToCartCatalogItem ) { - const data = await wixClient.currentCart.addToCurrentCart({ - lineItems: [item], + const data = await wixClient.currentCartV2.addLineItemsToCurrentCart({ + catalogItems: [item], }); - if (!data?.cart?.overrideCheckoutUrl) { - void wixClient.currentCart.updateCurrentCart({ - cartInfo: { - overrideCheckoutUrl: `${window.location.origin}/api/redirect-to-checkout?checkoutId={checkoutId}`, - }, + if (!data?.cart?.customCheckoutUrl) { + // Cart V2 renamed overrideCheckoutUrl -> customCheckoutUrl, and updateCurrentCart + // takes the Cart object directly (no cartInfo wrapper). + void wixClient.currentCartV2.updateCurrentCart({ + customCheckoutUrl: `${window.location.origin}/api/redirect-to-checkout?checkoutId={checkoutId}`, }); } return data; diff --git a/nextjs/commerce-ticketing/app/hooks/useCart.tsx b/nextjs/commerce-ticketing/app/hooks/useCart.tsx index ac481626..7439f813 100644 --- a/nextjs/commerce-ticketing/app/hooks/useCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useCart.tsx @@ -3,7 +3,14 @@ import { useWixClient } from './useWixClient'; export const useCart = () => { const wixClient = useWixClient(); - return useQuery(['cart'], () => wixClient.currentCart.getCurrentCart(), { - retry: false, - }); + return useQuery( + ['cart'], + async () => { + const { cart } = await wixClient.currentCartV2.getCurrentCart(); + return cart; + }, + { + retry: false, + } + ); }; diff --git a/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx b/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx index 3b441967..1d1e2e6e 100644 --- a/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx @@ -16,5 +16,7 @@ export const useRemoveItemFromCart = () => { }; async function removeItemFromCart(wixClient: WixClient, itemId: string) { - return wixClient.currentCart.removeLineItemsFromCurrentCart([itemId]); + return wixClient.currentCartV2.removeLineItemsFromCurrentCart({ + lineItemIds: [itemId], + }); } diff --git a/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx b/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx index becffd05..57572aa4 100644 --- a/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx @@ -1,14 +1,16 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { currentCart } from '@wix/ecom'; import { WixClient } from '@app/components/Provider/ClientProvider'; import { useWixClient } from './useWixClient'; +// Cart V2 quantity update: identify the line by `_id` and set the new quantity. +type LineItemQuantityUpdate = { _id: string; quantity: number }; + export const useUpdateCart = () => { const wixClient = useWixClient(); const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: (item: currentCart.LineItemQuantityUpdate) => + mutationFn: (item: LineItemQuantityUpdate) => updateLineItemQuantity(wixClient, item), onSuccess: (data) => { queryClient.setQueryData(['cart'], data.cart); @@ -19,7 +21,11 @@ export const useUpdateCart = () => { async function updateLineItemQuantity( wixClient: WixClient, - item: currentCart.LineItemQuantityUpdate + item: LineItemQuantityUpdate ) { - return wixClient.currentCart.updateCurrentCartLineItemQuantity([item]); + return wixClient.currentCartV2.updateLineItemsInCurrentCart({ + lineItems: [ + { lineItemId: item._id, quantity: { newQuantity: item.quantity } }, + ], + }); } diff --git a/nextjs/commerce-ticketing/app/hooks/useWixClientServer.ts b/nextjs/commerce-ticketing/app/hooks/useWixClientServer.ts index b99f7a7c..f1b020ab 100644 --- a/nextjs/commerce-ticketing/app/hooks/useWixClientServer.ts +++ b/nextjs/commerce-ticketing/app/hooks/useWixClientServer.ts @@ -1,6 +1,6 @@ import { createClient, OAuthStrategy } from '@wix/sdk'; import { collections, productsV3 } from '@wix/stores'; -import { orders, currentCart, checkout as ecomCheckout } from '@wix/ecom'; +import { orders, currentCartV2, cartV2 } from '@wix/ecom'; import { redirects } from '@wix/redirects'; import { wixEventsV2 as wixEvents, @@ -23,11 +23,11 @@ export const getWixClient = async () => { productsV3, collections, wixEvents, - ecomCheckout, schedule, orders, eventOrders, - currentCart, + currentCartV2, + cartV2, redirects, }, auth: OAuthStrategy({ diff --git a/nextjs/commerce/lib/wix/index.ts b/nextjs/commerce/lib/wix/index.ts index 995a4f0f..75938bac 100644 --- a/nextjs/commerce/lib/wix/index.ts +++ b/nextjs/commerce/lib/wix/index.ts @@ -1,5 +1,5 @@ import { items } from "@wix/data"; -import { currentCart, recommendations } from "@wix/ecom"; +import { currentCartV2, recommendations } from "@wix/ecom"; import { redirects } from "@wix/redirects"; import { createClient, media, OAuthStrategy } from "@wix/sdk"; import { collections, products } from "@wix/stores"; @@ -12,7 +12,9 @@ const cartesian = (data: T[][]) => [], ] as T[][]); -const reshapeCart = (cart: currentCart.Cart): Cart => { +const reshapeCart = (cart: currentCartV2.Cart): Cart => { + const currency = + cart.customerInfo?.currencyCode ?? cart.businessInfo?.currencyCode!; return { id: cart._id!, checkoutUrl: "/cart-checkout", @@ -21,64 +23,69 @@ const reshapeCart = (cart: currentCart.Cart): Cart => { amount: String( cart.lineItems!.reduce((acc, item) => { return ( - acc + Number.parseFloat(item.price?.amount!) * item.quantity! + acc + + Number.parseFloat(item.pricing?.unitPrice?.amount!) * + item.quantityInfo?.confirmedQuantity! ); }, 0), ), - currencyCode: cart.currency!, + currencyCode: currency, }, totalAmount: { amount: String( cart.lineItems!.reduce((acc, item) => { return ( - acc + Number.parseFloat(item.price?.amount!) * item.quantity! + acc + + Number.parseFloat(item.pricing?.unitPrice?.amount!) * + item.quantityInfo?.confirmedQuantity! ); }, 0), ), - currencyCode: cart.currency!, + currencyCode: currency, }, totalTaxAmount: { amount: "0", - currencyCode: cart.currency!, + currencyCode: currency, }, }, lines: cart.lineItems!.map((item) => { - const featuredImage = media.getImageUrl(item.image!); + const featuredImage = media.getImageUrl(item.attributes?.image!); return { id: item._id!, - quantity: item.quantity!, + quantity: item.quantityInfo?.confirmedQuantity!, cost: { totalAmount: { amount: String( - Number.parseFloat(item.price?.amount!) * item.quantity!, + Number.parseFloat(item.pricing?.unitPrice?.amount!) * + item.quantityInfo?.confirmedQuantity!, ), - currencyCode: cart.currency!, + currencyCode: currency, }, }, merchandise: { id: item._id!, title: - item.descriptionLines + item.attributes?.descriptionLines ?.map((x) => x.colorInfo?.original ?? x.plainText?.original) .join(" / ") ?? "", selectedOptions: [], product: { - handle: item.url?.split("/").pop() ?? "", + handle: item.attributes?.url?.split("/").pop() ?? "", featuredImage: { altText: "altText" in featuredImage ? featuredImage.altText : "alt text", - url: media.getImageUrl(item.image!).url, - width: media.getImageUrl(item.image!).width, - height: media.getImageUrl(item.image!).height, + url: media.getImageUrl(item.attributes?.image!).url, + width: media.getImageUrl(item.attributes?.image!).width, + height: media.getImageUrl(item.attributes?.image!).height, }, - title: item.productName?.original!, + title: item.name?.original!, } as any as Product, - url: `/product/${item.url?.split("/").pop() ?? ""}`, + url: `/product/${item.attributes?.url?.split("/").pop() ?? ""}`, }, }; }), totalQuantity: cart.lineItems!.reduce((acc, item) => { - return acc + item.quantity!; + return acc + item.quantityInfo?.confirmedQuantity!; }, 0), }; }; @@ -194,9 +201,11 @@ const reshapeProduct = (item: products.Product) => { export async function addToCart( lines: { productId: string; variant?: ProductVariant; quantity: number }[], ): Promise { - const { addToCurrentCart } = (await getWixClient()).use(currentCart); - const { cart } = await addToCurrentCart({ - lineItems: lines.map(({ productId, variant, quantity }) => ({ + const { addLineItemsToCurrentCart } = (await getWixClient()).use( + currentCartV2, + ); + const { cart } = await addLineItemsToCurrentCart({ + catalogItems: lines.map(({ productId, variant, quantity }) => ({ catalogReference: { catalogItemId: productId, appId: "215238eb-22a5-4c36-9e7b-e7c08025e04e", @@ -224,10 +233,12 @@ export async function addToCart( export async function removeFromCart(lineIds: string[]): Promise { const { removeLineItemsFromCurrentCart } = (await getWixClient()).use( - currentCart, + currentCartV2, ); - const { cart } = await removeLineItemsFromCurrentCart(lineIds); + const { cart } = await removeLineItemsFromCurrentCart({ + lineItemIds: lineIds, + }); return reshapeCart(cart!); } @@ -235,26 +246,26 @@ export async function removeFromCart(lineIds: string[]): Promise { export async function updateCart( lines: { id: string; quantity: number }[], ): Promise { - const { updateCurrentCartLineItemQuantity } = (await getWixClient()).use( - currentCart, + const { updateLineItemsInCurrentCart } = (await getWixClient()).use( + currentCartV2, ); - const { cart } = await updateCurrentCartLineItemQuantity( - lines.map(({ id, quantity }) => ({ - id: id, - quantity, + const { cart } = await updateLineItemsInCurrentCart({ + lineItems: lines.map(({ id, quantity }) => ({ + lineItemId: id, + quantity: { newQuantity: quantity }, })), - ); + }); return reshapeCart(cart!); } export async function getCart(): Promise { - const { getCurrentCart } = (await getWixClient()).use(currentCart); + const { getCurrentCart } = (await getWixClient()).use(currentCartV2); try { - const cart = await getCurrentCart(); + const { cart } = await getCurrentCart(); - return reshapeCart(cart); + return reshapeCart(cart!); } catch (e) { if ((e as any)?.details?.applicationError?.code === "OWNED_CART_NOT_FOUND") { return undefined; @@ -540,16 +551,15 @@ export const getWixClient = async () => { export async function createCheckoutUrl(postFlowUrl: string) { const { - currentCart: { createCheckoutFromCurrentCart }, + currentCartV2: { getCurrentCart }, redirects: { createRedirectSession }, - } = (await getWixClient()).use({ currentCart, redirects }); + } = (await getWixClient()).use({ currentCartV2, redirects }); - const currentCheckout = await createCheckoutFromCurrentCart({ - channelType: currentCart.ChannelType.OTHER_PLATFORM, - }); + // Cart V2 has no separate checkout entity — the cart id is the checkout id. + const { cart } = await getCurrentCart(); const { redirectSession } = await createRedirectSession({ - ecomCheckout: { checkoutId: currentCheckout.checkoutId }, + ecomCheckout: { checkoutId: cart!._id! }, callbacks: { postFlowUrl, }, diff --git a/nextjs/minimal-examples/internal/utils/installed-apps.js b/nextjs/minimal-examples/internal/utils/installed-apps.js index feafafb6..fedffc6a 100644 --- a/nextjs/minimal-examples/internal/utils/installed-apps.js +++ b/nextjs/minimal-examples/internal/utils/installed-apps.js @@ -4,7 +4,7 @@ import {redirects} from "@wix/redirects"; import Cookies from "js-cookie"; import {availabilityCalendar, services} from "@wix/bookings"; import {products} from "@wix/stores"; -import {currentCart} from "@wix/ecom"; +import {currentCartV2} from "@wix/ecom"; import {plans} from "@wix/pricing-plans"; import {orders as checkout, wixEventsV2 as wixEvents} from "@wix/events"; import {jwtDecode} from "jwt-decode"; @@ -25,7 +25,7 @@ const createWixClient = () => { availabilityCalendar, redirects, products, - currentCart, + currentCartV2, plans, checkout, wixEvents, @@ -63,11 +63,11 @@ const checkStoresInstalled = async (myWixClient) => { {}, // This is the initial value of the reduce function. It's an empty object that we'll add properties to. ); - // Then, we call the addToCurrentCart method from the currentCart module of the Wix client. + // Then, we call the addLineItemsToCurrentCart method from the currentCartV2 module of the Wix client. // This method adds items to the current user's shopping cart. - await myWixClient.currentCart.addToCurrentCart({ + await myWixClient.currentCartV2.addLineItemsToCurrentCart({ // We pass an object that describes the product to be added. - lineItems: [ + catalogItems: [ { // Each product is identified by a catalogReference object. catalogReference: { @@ -80,8 +80,8 @@ const checkStoresInstalled = async (myWixClient) => { ], }); - const cartHasItems = (await myWixClient.currentCart.getCurrentCart()).lineItems.length > 0; - await myWixClient.currentCart.deleteCurrentCart(); + const cartHasItems = (await myWixClient.currentCartV2.getCurrentCart()).cart.lineItems.length > 0; + await myWixClient.currentCartV2.deleteCurrentCart(); return cartHasItems; } catch (error) { return false; diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index f1e5557d..4afd6388 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -3,7 +3,7 @@ import {useEffect, useState} from "react"; import {createClient, OAuthStrategy} from "@wix/sdk"; import {products} from "@wix/stores"; -import {currentCart} from "@wix/ecom"; +import {currentCartV2} from "@wix/ecom"; import {redirects} from "@wix/redirects"; import testIds from "@/src/utils/test-ids"; import {CLIENT_ID} from "@/constants/constants"; @@ -17,8 +17,8 @@ import {useModal} from "@/internal/providers/modal-provider"; // We're creating a Wix client using the createClient function from the Wix SDK. const myWixClient = createClient({ // We specify the modules we want to use with the client. - // In this case, we're using the products, currentCart, and redirects modules. - modules: {products, currentCart, redirects}, + // In this case, we're using the products, currentCartV2, and redirects modules. + modules: {products, currentCartV2, redirects}, // We're using the OAuthStrategy for authentication. // This strategy requires a client ID and a set of tokens. @@ -38,6 +38,7 @@ export default function Store() { // State variables for product list and cart const [productList, setProductList] = useState([]); const [cart, setCart] = useState({}); + const [subtotal, setSubtotal] = useState(""); const [isLoading, setIsLoading] = useState(true); const handleAsync = useAsyncHandler(); const {msid} = useClient(); @@ -63,11 +64,20 @@ export default function Store() { async function fetchCart() { // try-catch block to handle errors if the cart is not available try { - // We call the getCurrentCart method from the currentCart module of the Wix client. - // This method retrieves the current user's shopping cart. - await handleAsync(async () => - setCart(await myWixClient.currentCart.getCurrentCart()), - ); + // We call the getCurrentCart method from the currentCartV2 module of the Wix client. + // This method retrieves the current user's shopping cart. V2 returns { cart }. + await handleAsync(async () => { + const {cart} = await myWixClient.currentCartV2.getCurrentCart(); + setCart(cart); + // V2's cart entity has no preformatted subtotal; estimate the cart to get + // a formatted price summary. Empty carts may throw, so ignore failures. + const estimate = await myWixClient.currentCartV2 + .estimateCurrentCart() + .catch(() => null); + setSubtotal( + estimate?.summary?.priceSummary?.subtotal?.formattedAmount ?? "", + ); + }); } catch { // If the cart is not available, do something (e.g., show an error message) } @@ -97,17 +107,17 @@ export default function Store() { if (existingProduct) { return addExistingProduct( existingProduct._id, - existingProduct.quantity + 1, + existingProduct.quantityInfo.confirmedQuantity + 1, ); } } - // Then, we call the addToCurrentCart method from the currentCart module of the Wix client. + // Then, we call the addLineItemsToCurrentCart method from the currentCartV2 module of the Wix client. // This method adds items to the current user's shopping cart. const {cart: returnedCard} = - await myWixClient.currentCart.addToCurrentCart({ + await myWixClient.currentCartV2.addLineItemsToCurrentCart({ // We pass an object that describes the product to be added. - lineItems: [ + catalogItems: [ { // Each product is identified by a catalogReference object. catalogReference: { @@ -128,12 +138,13 @@ export default function Store() { // This is a function that clears the cart. async function clearCart() { await handleAsync(async () => { - // We call the deleteCurrentCart method from the currentCart module of the Wix client. + // We call the deleteCurrentCart method from the currentCartV2 module of the Wix client. // This method deletes the current site visitor's shopping cart. - await myWixClient.currentCart.deleteCurrentCart(); + await myWixClient.currentCartV2.deleteCurrentCart(); // Then, we update the state of the cart in the React component to be an empty object. setCart({}); + setSubtotal(""); }); } @@ -141,19 +152,15 @@ export default function Store() { async function createRedirect() { try { await handleAsync(async () => { - // We call the createCheckoutFromCurrentCart method from the currentCart module of the Wix client. - // This method creates a checkout from the current user's shopping cart. - const {checkoutId} = - await myWixClient.currentCart.createCheckoutFromCurrentCart({ - // We specify the channel type to be WEB. - channelType: currentCart.ChannelType.WEB, - }); + // Cart V2 has no separate checkout entity — the cart id IS the checkout id. + // So we read the current cart and use its _id directly; no create-checkout call. + const {cart} = await myWixClient.currentCartV2.getCurrentCart(); // Then, we call the createRedirectSession method from the redirects module of the Wix client. // This method creates a redirect session to the checkout page. const redirect = await myWixClient.redirects.createRedirectSession({ // We pass an object that specifies the checkoutId for the ecomCheckout. - ecomCheckout: {checkoutId}, + ecomCheckout: {checkoutId: cart._id}, // We also specify the postFlowUrl to be the current page URL. This is where the user will be redirected after the checkout flow. callbacks: {postFlowUrl: window.location.href}, }); @@ -175,12 +182,14 @@ export default function Store() { async function addExistingProduct(lineItemId, quantity) { const {cart} = - await myWixClient.currentCart.updateCurrentCartLineItemQuantity([ - { - _id: lineItemId, - quantity, - }, - ]); + await myWixClient.currentCartV2.updateLineItemsInCurrentCart({ + lineItems: [ + { + lineItemId, + quantity: {newQuantity: quantity}, + }, + ], + }); // Finally, we update the state of the cart in the React component. setCart(cart); @@ -256,16 +265,16 @@ export default function Store() {
    - {item.quantity} + {item.quantityInfo.confirmedQuantity}
    - {item.productName.original} + {item.name.original}
))} -

Total {cart.subtotal.formattedAmount}

+

Total {subtotal}

@@ -90,7 +90,7 @@ export const CartItem = ({ {slug ? ( - {item.name?.translated} + {item.name?.translated ?? item.name?.original} ) : ( diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index 4afd6388..ce76f083 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -69,13 +69,23 @@ export default function Store() { await handleAsync(async () => { const {cart} = await myWixClient.currentCartV2.getCurrentCart(); setCart(cart); - // V2's cart entity has no preformatted subtotal; estimate the cart to get - // a formatted price summary. Empty carts may throw, so ignore failures. + // V2 money is raw ConvertedMoney (no formatted string), so estimate the + // cart for the subtotal amount and format it client-side. Empty carts may throw. const estimate = await myWixClient.currentCartV2 .estimateCurrentCart() .catch(() => null); + const sub = estimate?.summary?.priceSummary?.subtotal; + const currency = + cart?.customerInfo?.currencyCode ?? + cart?.businessInfo?.currencyCode ?? + "USD"; setSubtotal( - estimate?.summary?.priceSummary?.subtotal?.formattedAmount ?? "", + sub?.amount != null + ? new Intl.NumberFormat(undefined, { + style: "currency", + currency, + }).format(Number(sub.convertedAmount ?? sub.amount)) + : "", ); }); } catch { From 32f7434200d6523e633e230fa1f362f5d3b740b9 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 11:41:53 +0300 Subject: [PATCH 04/13] fix(templates): address 2nd independent review (2 MAJOR + minor/nit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - minimal-examples/store.js: subtotal went stale after add/update (computed only on mount) — re-fetch (cart + estimate) after each mutation - react-native ProductScreen "Buy Now": was polluting the persistent current cart; now creates an isolated cart via cartV2.createCart and redirects to it (register cartV2 in wixClient) - prefer pricing.convertedAmount ?? amount for display-currency correctness (nextjs/commerce reshapeCart, commerce-ticketing CartView/CartItem) - CartItem: name?.translated ?? name?.original in the non-linked branch too Co-Authored-By: Claude Opus 4.8 --- .../app/components/Cart/CartView.tsx | 9 +++++- .../app/components/CartItem/CartItem.tsx | 6 ++-- nextjs/commerce/lib/wix/index.ts | 6 ++-- nextjs/minimal-examples/pages/store.js | 29 +++++++++---------- .../authentication/wixClient.js | 3 +- .../screens/store/product/ProductScreen.js | 9 +++--- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx index 9c6b52b1..f52ed638 100644 --- a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx +++ b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx @@ -20,7 +20,14 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { amount: data.lineItems?.reduce((acc, item) => { // V2 `totalPrice` is already the line total (unit x quantity). - return acc + Number(item.pricing?.totalPrice?.amount ?? 0); + return ( + acc + + Number( + item.pricing?.totalPrice?.convertedAmount ?? + item.pricing?.totalPrice?.amount ?? + 0, + ) + ); }, 0) ?? 0, currencyCode, } diff --git a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx index 79aafdd4..b10e115e 100644 --- a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx +++ b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx @@ -28,7 +28,9 @@ export const CartItem = ({ const updateCartMutation = useUpdateCart(); // V2 pricing: `totalPrice` is already the line total (unit x quantity). - const lineTotal = Number(item.pricing?.totalPrice?.amount ?? 0); + const lineTotal = Number( + item.pricing?.totalPrice?.convertedAmount ?? item.pricing?.totalPrice?.amount ?? 0, + ); const price = formatPrice({ amount: lineTotal, baseAmount: lineTotal, @@ -95,7 +97,7 @@ export const CartItem = ({ ) : ( - {item.name?.translated} + {item.name?.translated ?? item.name?.original} )}
diff --git a/nextjs/commerce/lib/wix/index.ts b/nextjs/commerce/lib/wix/index.ts index 75938bac..da0c4076 100644 --- a/nextjs/commerce/lib/wix/index.ts +++ b/nextjs/commerce/lib/wix/index.ts @@ -24,7 +24,7 @@ const reshapeCart = (cart: currentCartV2.Cart): Cart => { cart.lineItems!.reduce((acc, item) => { return ( acc + - Number.parseFloat(item.pricing?.unitPrice?.amount!) * + Number.parseFloat((item.pricing?.unitPrice?.convertedAmount ?? item.pricing?.unitPrice?.amount)!) * item.quantityInfo?.confirmedQuantity! ); }, 0), @@ -36,7 +36,7 @@ const reshapeCart = (cart: currentCartV2.Cart): Cart => { cart.lineItems!.reduce((acc, item) => { return ( acc + - Number.parseFloat(item.pricing?.unitPrice?.amount!) * + Number.parseFloat((item.pricing?.unitPrice?.convertedAmount ?? item.pricing?.unitPrice?.amount)!) * item.quantityInfo?.confirmedQuantity! ); }, 0), @@ -56,7 +56,7 @@ const reshapeCart = (cart: currentCartV2.Cart): Cart => { cost: { totalAmount: { amount: String( - Number.parseFloat(item.pricing?.unitPrice?.amount!) * + Number.parseFloat((item.pricing?.unitPrice?.convertedAmount ?? item.pricing?.unitPrice?.amount)!) * item.quantityInfo?.confirmedQuantity!, ), currencyCode: currency, diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index ce76f083..14c7e2aa 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -124,8 +124,7 @@ export default function Store() { // Then, we call the addLineItemsToCurrentCart method from the currentCartV2 module of the Wix client. // This method adds items to the current user's shopping cart. - const {cart: returnedCard} = - await myWixClient.currentCartV2.addLineItemsToCurrentCart({ + await myWixClient.currentCartV2.addLineItemsToCurrentCart({ // We pass an object that describes the product to be added. catalogItems: [ { @@ -140,8 +139,9 @@ export default function Store() { ], }); - // Finally, we update the state of the cart in the React component. - setCart(returnedCard); + // Re-fetch so the line list AND the subtotal refresh — V2 stores no total on + // the cart, so the subtotal is derived from a fresh estimateCurrentCart. + await fetchCart(); }); } @@ -191,18 +191,17 @@ export default function Store() { } async function addExistingProduct(lineItemId, quantity) { - const {cart} = - await myWixClient.currentCartV2.updateLineItemsInCurrentCart({ - lineItems: [ - { - lineItemId, - quantity: {newQuantity: quantity}, - }, - ], - }); + await myWixClient.currentCartV2.updateLineItemsInCurrentCart({ + lineItems: [ + { + lineItemId, + quantity: {newQuantity: quantity}, + }, + ], + }); - // Finally, we update the state of the cart in the React component. - setCart(cart); + // Re-fetch so the line list and subtotal both refresh. + await fetchCart(); } // Fetch products and cart on component mount diff --git a/react-native/mobile-ecommerce/authentication/wixClient.js b/react-native/mobile-ecommerce/authentication/wixClient.js index 0f8d9036..1d6fcf43 100644 --- a/react-native/mobile-ecommerce/authentication/wixClient.js +++ b/react-native/mobile-ecommerce/authentication/wixClient.js @@ -1,7 +1,7 @@ import { createClient, OAuthStrategy } from "@wix/sdk"; import { products, collections } from "@wix/stores"; import { members } from "@wix/members"; -import { currentCartV2, orders } from "@wix/ecom"; +import { cartV2, currentCartV2, orders } from "@wix/ecom"; import { redirects } from "@wix/redirects"; const clientId = process.env.EXPO_PUBLIC_WIX_CLIENT_ID || ""; @@ -14,6 +14,7 @@ export const wixCient = createClient({ products, collections, members, + cartV2, currentCartV2, redirects, orders, diff --git a/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js b/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js index 1fd906e9..fe4eef94 100644 --- a/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js +++ b/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js @@ -46,9 +46,10 @@ export function ProductScreen({ route, navigation }) { const buyNowMutation = useMutation( async (quantity) => { - // Cart V2 has no separate checkout entity: add the item to the current - // cart, then use the cart's own _id as the checkout id for the redirect. - await wixCient.currentCartV2.addLineItemsToCurrentCart({ + // "Buy Now" is an isolated purchase: create a standalone cart with just this + // item (so the shopper's current cart is untouched), then use that cart's _id + // as the checkout id for the redirect (Cart V2 has no separate checkout entity). + const cart = await wixCient.cartV2.createCart({ catalogItems: [ { quantity, @@ -61,8 +62,6 @@ export function ProductScreen({ route, navigation }) { ], }); - const { cart } = await wixCient.currentCartV2.getCurrentCart(); - const { redirectSession } = await wixCient.redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, From b012a9bc33116f82d11a25a1fe4fe7e473f32b7f Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 11:54:35 +0300 Subject: [PATCH 05/13] =?UTF-8?q?fix(templates):=203rd-review=20=E2=80=94?= =?UTF-8?q?=20remove-line-items=20signature=20+=20V2LineItem=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BLOCKER: removeLineItemsFromCurrentCart takes a positional string[] in the SDK (asymmetric vs add/update which take options objects). The { lineItemIds } object double-wrapped the REST body and broke removal + failed TS build. Fixed in commerce-ticketing (useRemoveItemFromCart), nextjs/commerce (lib/wix), and react-native (CartScreen). MAJOR: cartV2.LineItem is a violation/suggested-fix type; the cart line item is cartV2.V2LineItem. Fixed the type annotations in CartBag and CartItem so the ticketing template type-checks. Co-Authored-By: Claude Opus 4.8 --- nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx | 2 +- .../commerce-ticketing/app/components/CartItem/CartItem.tsx | 2 +- .../commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx | 4 +--- nextjs/commerce/lib/wix/index.ts | 4 +--- .../mobile-ecommerce/screens/store/cart/CartScreen.js | 5 ++--- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx index 37dfa8af..f430fffc 100644 --- a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx +++ b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx @@ -8,7 +8,7 @@ export const CartBag = () => { const { data, isLoading } = useCart(); const itemsCount = !isLoading ? data?.lineItems?.reduce( - (count: number, item: cartV2.LineItem) => + (count: number, item: cartV2.V2LineItem) => count + (item.quantityInfo?.confirmedQuantity ?? 0), 0 ) diff --git a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx index b10e115e..57c7c50c 100644 --- a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx +++ b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx @@ -15,7 +15,7 @@ export const CartItem = ({ hideButtons, ...rest }: { - item: cartV2.LineItem; + item: cartV2.V2LineItem; currencyCode: string; hideButtons?: boolean; }) => { diff --git a/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx b/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx index 1d1e2e6e..63ba8403 100644 --- a/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useRemoveItemFromCart.tsx @@ -16,7 +16,5 @@ export const useRemoveItemFromCart = () => { }; async function removeItemFromCart(wixClient: WixClient, itemId: string) { - return wixClient.currentCartV2.removeLineItemsFromCurrentCart({ - lineItemIds: [itemId], - }); + return wixClient.currentCartV2.removeLineItemsFromCurrentCart([itemId]); } diff --git a/nextjs/commerce/lib/wix/index.ts b/nextjs/commerce/lib/wix/index.ts index da0c4076..3e1b7def 100644 --- a/nextjs/commerce/lib/wix/index.ts +++ b/nextjs/commerce/lib/wix/index.ts @@ -236,9 +236,7 @@ export async function removeFromCart(lineIds: string[]): Promise { currentCartV2, ); - const { cart } = await removeLineItemsFromCurrentCart({ - lineItemIds: lineIds, - }); + const { cart } = await removeLineItemsFromCurrentCart(lineIds); return reshapeCart(cart!); } diff --git a/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js b/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js index a6c36a4d..1d5d386a 100644 --- a/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js +++ b/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js @@ -85,9 +85,8 @@ function CartItem({ item, currency }) { const removeMutation = useMutation( async () => { - return wixCient.currentCartV2.removeLineItemsFromCurrentCart({ - lineItemIds: [item._id], - }); + // SDK signature is positional (lineItemIds: string[]), unlike add/update which take options objects. + return wixCient.currentCartV2.removeLineItemsFromCurrentCart([item._id]); }, { onSuccess: (response) => { From e8783369eaacbc1570d8f7dfc960b6c4b334c11d Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 12:05:13 +0300 Subject: [PATCH 06/13] =?UTF-8?q?fix(templates):=204th-review=20=E2=80=94?= =?UTF-8?q?=201=20BLOCKER=20+=202=20MAJOR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - minimal-examples/store.js: item.catalogReference (top-level) threw on any non-empty cart; V2 nests it at item.source.catalogReference (BLOCKER) - commerce-ticketing CartItem: item.descriptionLines -> item.attributes .descriptionLines (TS error + silently non-rendering) (MAJOR) - customCheckoutUrl placeholder {checkoutId} -> {checkout_id} in quick-buy + useAddItemToCart; V2 only substitutes the snake_case token (MAJOR) Co-Authored-By: Claude Opus 4.8 --- .../commerce-ticketing/app/api/quick-buy/[productId]/route.ts | 2 +- .../commerce-ticketing/app/components/CartItem/CartItem.tsx | 4 ++-- nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx | 2 +- nextjs/minimal-examples/pages/store.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts index ecd024cf..d249c6df 100644 --- a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts +++ b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts @@ -55,7 +55,7 @@ export async function GET( // id, so we redirect straight from the created cart (no createCheckout call). const cart = await wixClient.cartV2.createCart({ cart: { - customCheckoutUrl: `${baseUrl}api/redirect-to-checkout?checkoutId={checkoutId}`, + customCheckoutUrl: `${baseUrl}api/redirect-to-checkout?checkoutId={checkout_id}`, }, catalogItems: [item], }); diff --git a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx index 57c7c50c..d3d75392 100644 --- a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx +++ b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx @@ -102,9 +102,9 @@ export const CartItem = ({ )} {price} - {item.descriptionLines?.length ? ( + {item.attributes?.descriptionLines?.length ? (
- {item.descriptionLines?.map((line) => ( + {item.attributes?.descriptionLines?.map((line) => ( customCheckoutUrl, and updateCurrentCart // takes the Cart object directly (no cartInfo wrapper). void wixClient.currentCartV2.updateCurrentCart({ - customCheckoutUrl: `${window.location.origin}/api/redirect-to-checkout?checkoutId={checkoutId}`, + customCheckoutUrl: `${window.location.origin}/api/redirect-to-checkout?checkoutId={checkout_id}`, }); } return data; diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index 14c7e2aa..eba7b7ff 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -110,7 +110,7 @@ export default function Store() { // Check if the product is already in the cart if (cart) { const existingProduct = cart?.lineItems?.find( - (item) => item.catalogReference.catalogItemId === product._id, + (item) => item.source?.catalogReference?.catalogItemId === product._id, ); // If the product is already in the cart, increase the quantity From e29f00e414548786984316413945822c6f1266e8 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 13:09:18 +0300 Subject: [PATCH 07/13] chore(commerce): loosen @wix/ecom pin so cartV2/currentCartV2 resolve astro/commerce pinned @wix/ecom exactly at 1.0.806 (pre-Cart-V2); caret it so install resolves a version exporting currentCartV2 (matches the other templates). Co-Authored-By: Claude Opus 4.8 --- astro/commerce/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astro/commerce/package.json b/astro/commerce/package.json index e609e9a6..f07e0e2f 100644 --- a/astro/commerce/package.json +++ b/astro/commerce/package.json @@ -20,7 +20,7 @@ "@wix/astro-pages": "^2.0.4", "@wix/dashboard": "^1.3.36", "@wix/data": "1.0.168", - "@wix/ecom": "1.0.806", + "@wix/ecom": "^1.0.806", "@wix/essentials": "^1.0.6", "@wix/members": "^1.0.496", "@wix/redirects": "1.0.60", From c7e03aeee635583abc344eb0b601399f45013b44 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 6 Aug 2026 14:47:02 +0300 Subject: [PATCH 08/13] fix(commerce-ticketing): guard optional cart in CartView checkout getCurrentCart() returns { cart?: Cart } (cart optional); CartView deref'd cart._id unguarded -> strict-null build error. Assert cart!._id (matches lib/wix). Caught by a type harness compiled against the real @wix/ecom types. Co-Authored-By: Claude Opus 4.8 --- nextjs/commerce-ticketing/app/components/Cart/CartView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx index f52ed638..29083c45 100644 --- a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx +++ b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx @@ -41,7 +41,7 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { const { cart } = await wixClient.currentCartV2.getCurrentCart(); const { redirectSession } = await wixClient.redirects.createRedirectSession({ - ecomCheckout: { checkoutId: cart._id }, + ecomCheckout: { checkoutId: cart!._id }, callbacks: { postFlowUrl: window.location.origin, thankYouPageUrl: `${window.location.origin}/stores-success`, From 6bf00c2a30e347cc52b1286d05f37b3813896cd3 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Sun, 9 Aug 2026 17:21:03 +0300 Subject: [PATCH 09/13] docs(templates): name the entity "cart", not "checkout" (V2 unification) - astro/commerce README: add the "one cart entity; checkout = the hosted page" framing note - astro/scheduler README: "@wix/ecom checkout" -> "@wix/ecom cart" (code uses cartV2.createCart) - minimal-examples README: "carts and checkouts" -> "carts and orders" Code comments keep their V1->V2 contrast + version-specific "Cart V2" notes (they help maintainers); identifiers untouched. Co-Authored-By: Claude Opus 4.8 --- astro/commerce/README.md | 2 ++ astro/scheduler/README.md | 2 +- nextjs/minimal-examples/README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/astro/commerce/README.md b/astro/commerce/README.md index f9475f65..f07bf1c0 100644 --- a/astro/commerce/README.md +++ b/astro/commerce/README.md @@ -6,6 +6,8 @@ A minimal Astro + React storefront wireframe backed by Wix Stores and Wix eComme ## How it connects to Wix +> Cart V2 is the evolution of the old cart + checkout: one **cart** now carries the whole purchase flow through placing the order. "Checkout" below means only the Wix-hosted checkout page the buyer is redirected to. + - **Catalog** — pages query products server-side with `@wix/stores` (`productsV3.queryProducts`). - **Cart** — the React island uses `@wix/ecom` `currentCartV2` to add items, read the cart, and estimate totals. - **Checkout** — Cart V2 has no separate checkout entity (the cart id is the checkout id), so `@wix/redirects` `createRedirectSession` is given the current cart's id to send the visitor to Wix Checkout. diff --git a/astro/scheduler/README.md b/astro/scheduler/README.md index 94a61f61..5b7bac69 100644 --- a/astro/scheduler/README.md +++ b/astro/scheduler/README.md @@ -6,7 +6,7 @@ An appointment scheduling template built with Astro and [Wix Bookings](https://d - Listing bookable services with `@wix/bookings` (`services.queryServices`) - Fetching availability in the visitor's timezone (`availabilityCalendar.queryAvailability`) -- Creating bookings for free services (`bookings.createBooking` + `@wix/ecom` checkout) +- Creating bookings for free services (`bookings.createBooking` + `@wix/ecom` cart) - Redirecting to the Wix-hosted checkout for paid services (`@wix/redirects`) The Wix integration logic lives in `src/utils/booking-service.ts`; pages are in `src/pages` (home, schedule, confirmation, 404). diff --git a/nextjs/minimal-examples/README.md b/nextjs/minimal-examples/README.md index bdd915a0..5244f6c0 100644 --- a/nextjs/minimal-examples/README.md +++ b/nextjs/minimal-examples/README.md @@ -67,7 +67,7 @@ services and their availability from your site. The [`pages/store.js`](./pages/store.js) file demonstrates how to fetch a list of products from your site using the [Wix Stores API](https://dev.wix.com/docs/sdk/backend-modules/stores). It also demonstrates how to use -the [Wix eCommerce API](https://dev.wix.com/docs/sdk/backend-modules/ecom/introduction) to manage carts and checkouts. +the [Wix eCommerce API](https://dev.wix.com/docs/sdk/backend-modules/ecom/introduction) to manage carts and orders. > **[Wix Stores API](https://dev.wix.com/docs/sdk/backend-modules/stores)**: This API allows you to manage your store > inventory, orders, and collections. From 09c1eccbf5561c8f7356fe2dfd18b42c7bb46f65 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Sun, 9 Aug 2026 17:59:03 +0300 Subject: [PATCH 10/13] docs(commerce): add migration-guide link to the astro/commerce README note Co-Authored-By: Claude Opus 4.8 --- astro/commerce/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astro/commerce/README.md b/astro/commerce/README.md index f07bf1c0..2a1d91c7 100644 --- a/astro/commerce/README.md +++ b/astro/commerce/README.md @@ -6,7 +6,7 @@ A minimal Astro + React storefront wireframe backed by Wix Stores and Wix eComme ## How it connects to Wix -> Cart V2 is the evolution of the old cart + checkout: one **cart** now carries the whole purchase flow through placing the order. "Checkout" below means only the Wix-hosted checkout page the buyer is redirected to. +> Cart V2 is the evolution of the old cart + checkout: one **cart** now carries the whole purchase flow through placing the order. "Checkout" below means only the Wix-hosted checkout page the buyer is redirected to. Migrating from Cart V1 / Checkout V1? See the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide). - **Catalog** — pages query products server-side with `@wix/stores` (`productsV3.queryProducts`). - **Cart** — the React island uses `@wix/ecom` `currentCartV2` to add items, read the cart, and estimate totals. From 83f154df4f9f3464f11ac2195f99d207feff93bb Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 13 Aug 2026 15:24:48 +0300 Subject: [PATCH 11/13] docs(ecom): finish Cart V2-only pass across templates - Reframe comments/prose to describe Cart V2 as the only model; remove remaining Cart V1 / Checkout V1 references and V1<->V2 contrasts. One migration-guide referral in astro/commerce README where @wix/ecom is introduced. - Comment/prose only; no runtime behavior change (all edited code files still parse). Co-Authored-By: Claude Opus 4.8 --- astro/commerce/README.md | 7 ++++--- astro/commerce/src/components/AppIsland.jsx | 7 +++---- astro/scheduler/src/utils/booking-service.ts | 5 ++--- .../app/api/quick-buy/[productId]/route.ts | 4 ++-- .../commerce-ticketing/app/components/Cart/CartView.tsx | 2 +- nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx | 4 ++-- nextjs/commerce/lib/wix/index.ts | 2 +- nextjs/minimal-examples/pages/store.js | 4 ++-- .../mobile-ecommerce/screens/store/cart/CartScreen.js | 9 ++++----- .../screens/store/product/ProductScreen.js | 2 +- 10 files changed, 22 insertions(+), 24 deletions(-) diff --git a/astro/commerce/README.md b/astro/commerce/README.md index 2a1d91c7..3a290172 100644 --- a/astro/commerce/README.md +++ b/astro/commerce/README.md @@ -6,11 +6,12 @@ A minimal Astro + React storefront wireframe backed by Wix Stores and Wix eComme ## How it connects to Wix -> Cart V2 is the evolution of the old cart + checkout: one **cart** now carries the whole purchase flow through placing the order. "Checkout" below means only the Wix-hosted checkout page the buyer is redirected to. Migrating from Cart V1 / Checkout V1? See the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide). - - **Catalog** — pages query products server-side with `@wix/stores` (`productsV3.queryProducts`). - **Cart** — the React island uses `@wix/ecom` `currentCartV2` to add items, read the cart, and estimate totals. -- **Checkout** — Cart V2 has no separate checkout entity (the cart id is the checkout id), so `@wix/redirects` `createRedirectSession` is given the current cart's id to send the visitor to Wix Checkout. + +> Migrating from Cart V1 / Checkout V1? This template is V2-only — see the migration guide: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide + +- **Checkout** — the cart id is the checkout id, so `@wix/redirects` `createRedirectSession` is given the current cart's id to send the visitor to Wix Checkout. - **Members** — `@wix/members` reads the current member; login/logout go through the built-in `/api/auth/*` routes. - **Media** — product images are scaled with `media.getScaledToFillImageUrl` from `@wix/sdk`. diff --git a/astro/commerce/src/components/AppIsland.jsx b/astro/commerce/src/components/AppIsland.jsx index 5555951e..07a6ebd0 100644 --- a/astro/commerce/src/components/AppIsland.jsx +++ b/astro/commerce/src/components/AppIsland.jsx @@ -6,9 +6,8 @@ import { media } from '@wix/sdk'; // Public app id of the Wix Stores catalog, used in ecom catalog references. const WIX_STORES_APP_ID = '215238eb-22a5-4c36-9e7b-e7c08025e04e'; -// Cart V2 line-item prices are raw decimal strings (ConvertedMoney: { amount, convertedAmount }), -// not preformatted display strings like Cart V1's MultiCurrencyPrice. Format them client-side -// from the amount + the cart's currency code. +// Cart V2 line-item prices are raw decimal strings (ConvertedMoney: { amount, convertedAmount }). +// Format them client-side from the amount + the cart's currency code. function formatMoney(money, currencyCode) { const value = money?.convertedAmount ?? money?.amount; if (value == null) return ''; @@ -156,7 +155,7 @@ function CartPanel({ onClose }) { async function handleCheckout() { setCheckingOut(true); try { - // Cart V2 has no separate checkout entity — the cart id is the checkout id. + // The cart id is the checkout id. // We still use a redirect session so the visitor/member session carries across // to the Wix-hosted checkout on its own domain. const { cart } = await currentCartV2.getCurrentCart(); diff --git a/astro/scheduler/src/utils/booking-service.ts b/astro/scheduler/src/utils/booking-service.ts index 8b6985a0..6bbb2b55 100644 --- a/astro/scheduler/src/utils/booking-service.ts +++ b/astro/scheduler/src/utils/booking-service.ts @@ -157,9 +157,8 @@ export async function createBooking( }, }); - // Cart V2 unifies cart + checkout: create a cart, calculate it to get the - // price-verification token, then place the order (replaces Checkout V1's - // createCheckout + createOrder). + // Create a cart, calculate it to get the price-verification token, then + // place the order. const createdCart = await cartV2.createCart({ cart: { source: { channelType: "WEB" }, diff --git a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts index d249c6df..6cb9803b 100644 --- a/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts +++ b/nextjs/commerce-ticketing/app/api/quick-buy/[productId]/route.ts @@ -51,8 +51,8 @@ export async function GET( options: selectedOptions, }, }; - // Cart V2: create a fresh cart for this quick-buy. The cart id IS the checkout - // id, so we redirect straight from the created cart (no createCheckout call). + // Create a fresh cart for this quick-buy. The cart id IS the checkout + // id, so we redirect straight from the created cart. const cart = await wixClient.cartV2.createCart({ cart: { customCheckoutUrl: `${baseUrl}api/redirect-to-checkout?checkoutId={checkout_id}`, diff --git a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx index 29083c45..87d8067e 100644 --- a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx +++ b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx @@ -37,7 +37,7 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { closeSidebar(); setRedirecting(true); try { - // Cart V2 has no separate checkout entity: the cart id IS the checkout id. + // The cart id IS the checkout id. const { cart } = await wixClient.currentCartV2.getCurrentCart(); const { redirectSession } = await wixClient.redirects.createRedirectSession({ diff --git a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx index 22466f23..e268e554 100644 --- a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx @@ -35,8 +35,8 @@ async function addItemFromCart( catalogItems: [item], }); if (!data?.cart?.customCheckoutUrl) { - // Cart V2 renamed overrideCheckoutUrl -> customCheckoutUrl, and updateCurrentCart - // takes the Cart object directly (no cartInfo wrapper). + // Set customCheckoutUrl; updateCurrentCart takes the Cart object directly + // (no cartInfo wrapper). void wixClient.currentCartV2.updateCurrentCart({ customCheckoutUrl: `${window.location.origin}/api/redirect-to-checkout?checkoutId={checkout_id}`, }); diff --git a/nextjs/commerce/lib/wix/index.ts b/nextjs/commerce/lib/wix/index.ts index 3e1b7def..53f5eb74 100644 --- a/nextjs/commerce/lib/wix/index.ts +++ b/nextjs/commerce/lib/wix/index.ts @@ -553,7 +553,7 @@ export async function createCheckoutUrl(postFlowUrl: string) { redirects: { createRedirectSession }, } = (await getWixClient()).use({ currentCartV2, redirects }); - // Cart V2 has no separate checkout entity — the cart id is the checkout id. + // The cart id is the checkout id. const { cart } = await getCurrentCart(); const { redirectSession } = await createRedirectSession({ diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index eba7b7ff..8f8e0a38 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -162,8 +162,8 @@ export default function Store() { async function createRedirect() { try { await handleAsync(async () => { - // Cart V2 has no separate checkout entity — the cart id IS the checkout id. - // So we read the current cart and use its _id directly; no create-checkout call. + // The cart id IS the checkout id. + // So we read the current cart and use its _id directly. const {cart} = await myWixClient.currentCartV2.getCurrentCart(); // Then, we call the createRedirectSession method from the redirects module of the Wix client. diff --git a/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js b/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js index 1d5d386a..cc502350 100644 --- a/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js +++ b/react-native/mobile-ecommerce/screens/store/cart/CartScreen.js @@ -133,9 +133,8 @@ function CartView() { setCheckoutRedirect(true); setTriggerInvalidCoupon(false); - // Cart V1 applied the buyer note + coupon on the checkout entity via - // updateCheckout. Cart V2 applies them on the cart itself: the note via - // updateCurrentCart, the coupon via the dedicated addCouponToCurrentCart. + // Apply the buyer note via updateCurrentCart and the coupon via the + // dedicated addCouponToCurrentCart (both on the cart). if (userNote) { await wixCient.currentCartV2.updateCurrentCart({ note: userNote }); } @@ -151,8 +150,8 @@ function CartView() { } } - // Cart V2 has no separate checkout entity: the current cart's _id IS the - // checkout id. Create a redirect session straight from it. + // The current cart's _id IS the checkout id. Create a redirect session + // straight from it. const { cart } = await wixCient.currentCartV2.getCurrentCart(); const { redirectSession } = diff --git a/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js b/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js index fe4eef94..253d5412 100644 --- a/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js +++ b/react-native/mobile-ecommerce/screens/store/product/ProductScreen.js @@ -48,7 +48,7 @@ export function ProductScreen({ route, navigation }) { async (quantity) => { // "Buy Now" is an isolated purchase: create a standalone cart with just this // item (so the shopper's current cart is untouched), then use that cart's _id - // as the checkout id for the redirect (Cart V2 has no separate checkout entity). + // as the checkout id for the redirect. const cart = await wixCient.cartV2.createCart({ catalogItems: [ { From c35ad5c939da7edb6362dc966659f2544b1a7e31 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Sun, 16 Aug 2026 11:50:27 +0300 Subject: [PATCH 12/13] fix(ecom): address review nits in cart-v2 templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - store.js: correct the subtotal comment — the cart keeps no *calculated* totals (they come from estimateCurrentCart -> summary.priceSummary); the cart's raw top-level subtotal isn't the discounted figure. - lib/wix/index.ts getCart(): guard the empty current cart (getCurrentCart returns { cart? }) and drop the cart! non-null assertion, so an absent cart returns undefined instead of reshaping undefined. - installed-apps.js: guard the cart.lineItems deref with optional chaining. Co-Authored-By: Claude Opus 4.8 --- nextjs/commerce/lib/wix/index.ts | 4 ++-- nextjs/minimal-examples/internal/utils/installed-apps.js | 2 +- nextjs/minimal-examples/pages/store.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nextjs/commerce/lib/wix/index.ts b/nextjs/commerce/lib/wix/index.ts index 53f5eb74..da51e363 100644 --- a/nextjs/commerce/lib/wix/index.ts +++ b/nextjs/commerce/lib/wix/index.ts @@ -262,8 +262,8 @@ export async function getCart(): Promise { const { getCurrentCart } = (await getWixClient()).use(currentCartV2); try { const { cart } = await getCurrentCart(); - - return reshapeCart(cart!); + if (!cart) return undefined; + return reshapeCart(cart); } catch (e) { if ((e as any)?.details?.applicationError?.code === "OWNED_CART_NOT_FOUND") { return undefined; diff --git a/nextjs/minimal-examples/internal/utils/installed-apps.js b/nextjs/minimal-examples/internal/utils/installed-apps.js index fedffc6a..b855f53c 100644 --- a/nextjs/minimal-examples/internal/utils/installed-apps.js +++ b/nextjs/minimal-examples/internal/utils/installed-apps.js @@ -80,7 +80,7 @@ const checkStoresInstalled = async (myWixClient) => { ], }); - const cartHasItems = (await myWixClient.currentCartV2.getCurrentCart()).cart.lineItems.length > 0; + const cartHasItems = ((await myWixClient.currentCartV2.getCurrentCart())?.cart?.lineItems?.length ?? 0) > 0; await myWixClient.currentCartV2.deleteCurrentCart(); return cartHasItems; } catch (error) { diff --git a/nextjs/minimal-examples/pages/store.js b/nextjs/minimal-examples/pages/store.js index 8f8e0a38..2f0e5797 100644 --- a/nextjs/minimal-examples/pages/store.js +++ b/nextjs/minimal-examples/pages/store.js @@ -139,8 +139,9 @@ export default function Store() { ], }); - // Re-fetch so the line list AND the subtotal refresh — V2 stores no total on - // the cart, so the subtotal is derived from a fresh estimateCurrentCart. + // Re-fetch so the line list AND the subtotal refresh — the cart keeps no + // calculated totals, so the displayed subtotal comes from a fresh + // estimateCurrentCart → summary.priceSummary. await fetchCart(); }); } From e76dda8d56e7352bbb68b60ff211942ab55c4ce7 Mon Sep 17 00:00:00 2001 From: rommy-amitai-w Date: Thu, 3 Sep 2026 15:17:38 +0300 Subject: [PATCH 13/13] fix(ecom): address final-review findings on the Cart V2 templates - commerce-ticketing CartView: document that Cart V2 enforces the payment-acceptance gate at place-order on the hosted checkout, so the SITE_MUST_ACCEPT_PAYMENTS branch is a best-effort client fallback. - Type cart line items as currentCartV2.V2LineItem (the module the data comes from) in CartBag/CartItem instead of the cross-module cartV2 alias. - Correct the stale hook comments: the V2 input types (currentCartV2.CatalogItemInput, LineItemUpdate/QuantityUpdate) do exist; the structural types are a deliberate decoupling, not an unverifiable guess. Co-Authored-By: Claude Opus 4.8 --- nextjs/commerce-ticketing/app/components/Cart/CartView.tsx | 4 ++++ .../commerce-ticketing/app/components/CartBag/CartBag.tsx | 4 ++-- .../commerce-ticketing/app/components/CartItem/CartItem.tsx | 4 ++-- nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx | 6 +++--- nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx | 1 + 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx index 87d8067e..e4023d3e 100644 --- a/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx +++ b/nextjs/commerce-ticketing/app/components/Cart/CartView.tsx @@ -52,6 +52,10 @@ export const CartView = ({ layout = 'mini' }: { layout?: 'full' | 'mini' }) => { window.location.href = redirectSession.fullUrl; } } catch (e: any) { + // In Cart V2 the "site must accept payments" gate is enforced at place-order on the + // Wix-hosted checkout — not on getCurrentCart/createRedirectSession — so a non-premium + // site surfaces the error there. This branch is kept as a best-effort fallback in case + // the SDK ever surfaces the code client-side. if ( e?.details?.applicationError?.code === 'SITE_MUST_ACCEPT_PAYMENTS_TO_CREATE_CHECKOUT' diff --git a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx index f430fffc..fa7b82e5 100644 --- a/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx +++ b/nextjs/commerce-ticketing/app/components/CartBag/CartBag.tsx @@ -1,14 +1,14 @@ 'use client'; import { useUI } from '@app/components/Provider/context'; import { useCart } from '@app/hooks/useCart'; -import { cartV2 } from '@wix/ecom'; +import { currentCartV2 } from '@wix/ecom'; export const CartBag = () => { const { setSidebarView, toggleSidebar } = useUI(); const { data, isLoading } = useCart(); const itemsCount = !isLoading ? data?.lineItems?.reduce( - (count: number, item: cartV2.V2LineItem) => + (count: number, item: currentCartV2.V2LineItem) => count + (item.quantityInfo?.confirmedQuantity ?? 0), 0 ) diff --git a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx index d3d75392..d930b4e1 100644 --- a/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx +++ b/nextjs/commerce-ticketing/app/components/CartItem/CartItem.tsx @@ -2,7 +2,7 @@ import { ChangeEvent, useEffect, useState } from 'react'; import Link from 'next/link'; import { formatPrice } from '@app/utils/price-formatter'; -import { cartV2 } from '@wix/ecom'; +import { currentCartV2 } from '@wix/ecom'; import { useUI } from '@app/components/Provider/context'; import { Quantity } from '@app/components/Quantity/Quantity'; import { useUpdateCart } from '@app/hooks/useUpdateCart'; @@ -15,7 +15,7 @@ export const CartItem = ({ hideButtons, ...rest }: { - item: cartV2.V2LineItem; + item: currentCartV2.V2LineItem; currencyCode: string; hideButtons?: boolean; }) => { diff --git a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx index e268e554..6278ba38 100644 --- a/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useAddItemToCart.tsx @@ -2,9 +2,9 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { WixClient } from '@app/components/Provider/ClientProvider'; import { useWixClient } from './useWixClient'; -// NOTE: Cart V2 catalog-item shape passed to `addLineItemsToCurrentCart`. -// The precise exported V2 type (likely `currentCartV2.CatalogItem`) could not be -// verified without installed deps, so a structural type is used to keep this compiling. +// Cart V2 catalog-item shape passed to `addLineItemsToCurrentCart` (the SDK type is +// `currentCartV2.CatalogItemInput`). A structural type is used here to keep the template +// decoupled from the SDK's generated typings. type AddToCartCatalogItem = { quantity?: number; catalogReference: { diff --git a/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx b/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx index 57572aa4..33c73de3 100644 --- a/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx +++ b/nextjs/commerce-ticketing/app/hooks/useUpdateCart.tsx @@ -3,6 +3,7 @@ import { WixClient } from '@app/components/Provider/ClientProvider'; import { useWixClient } from './useWixClient'; // Cart V2 quantity update: identify the line by `_id` and set the new quantity. +// (Built into the SDK's `currentCartV2.LineItemUpdate` / `QuantityUpdate` shape at call time.) type LineItemQuantityUpdate = { _id: string; quantity: number }; export const useUpdateCart = () => {