From 7b75b1d3b0d21a1c4874ec7aaf3a1d268fc7b2b8 Mon Sep 17 00:00:00 2001 From: Wender Lima Date: Thu, 30 Jul 2026 10:12:54 -0400 Subject: [PATCH 1/3] [B2BTEAM-3732] Forward priceToken on addToCart Pricing Fallback V2: fetch the signed price (commertialOffer.PriceToken) in the search-graphql product queries and forward it as priceToken in the addToCart payload, so the Checkout can close the cart while the Pricing is unavailable. Covers AutocompleteBlock and CategoryBlock. TextAreaBlock and UploadBlock resolve SKUs through the app's own skuFromRefIds resolver, which has no access to the token, and are left for a follow-up. The token is only added to the payload when the search actually returns one, keeping the payload unchanged while the field is not exposed yet. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 4 ++++ react/AutocompleteBlock.tsx | 36 ++++++++++++++++------------ react/CategoryBlock.tsx | 23 +++++++++++++++++- react/queries/product.gql | 3 +++ react/queries/productsByCategory.gql | 3 +++ 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1e2e98..a1532783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + +- Fetch `commertialOffer.PriceToken` in the product queries and forward it as `priceToken` on the `addToCart` payload of `AutocompleteBlock` and `CategoryBlock`, so the Checkout can close the cart with the signed price while the Pricing is unavailable (Pricing Fallback V2) + ## [3.16.8] - 2026-06-29 ### Fixed diff --git a/react/AutocompleteBlock.tsx b/react/AutocompleteBlock.tsx index cfd97b1a..c2a49437 100644 --- a/react/AutocompleteBlock.tsx +++ b/react/AutocompleteBlock.tsx @@ -187,12 +187,18 @@ const AutocompleteBlock: FunctionComponent = ({ const selectedSku = data.product.items.length === 1 ? data.product.items[0].itemId : null - const seller = selectedSku + const defaultSeller = selectedSku ? data.product.items[0].sellers.find((item: any) => { return item.sellerDefault === true - }).sellerId + }) : null + const seller = selectedSku ? defaultSeller.sellerId : null + + // Signed price from the search response, forwarded on addToCart so the + // Checkout can close the cart even while the Pricing is unavailable + const priceToken = defaultSeller?.commertialOffer?.PriceToken ?? null + let multiplier = 1 if (data.product.items.length === 1) { @@ -203,7 +209,7 @@ const AutocompleteBlock: FunctionComponent = ({ ...state, selectedItem: !!product && product.length - ? { ...product[0], value: selectedSku, seller, data } + ? { ...product[0], value: selectedSku, seller, priceToken, data } : null, unitMultiplier: multiplier, quantitySelected: multiplier, @@ -214,24 +220,21 @@ const AutocompleteBlock: FunctionComponent = ({ } const selectSku = (value: string) => { - const seller = selectedItem.data.product.items - .find((item: any) => { - return item.itemId === value - }) - .sellers.find((s: any) => { - return s.sellerDefault === true - }).sellerId + const matchedItem = selectedItem.data.product.items.find( + (item) => item.itemId === value + ) + + const defaultSeller = matchedItem.sellers.find((s: any) => { + return s.sellerDefault === true + }) const newSelected = { ...selectedItem, - seller, + seller: defaultSeller.sellerId, + priceToken: defaultSeller.commertialOffer?.PriceToken ?? null, value, } - const matchedItem = selectedItem.data.product.items.find( - (item) => item.itemId === value - ) - setState({ ...state, selectedItem: newSelected, @@ -262,6 +265,9 @@ const AutocompleteBlock: FunctionComponent = ({ unitMultiplier ), seller: selectedItem.seller, + ...(selectedItem.priceToken + ? { priceToken: selectedItem.priceToken } + : {}), }, ] diff --git a/react/CategoryBlock.tsx b/react/CategoryBlock.tsx index 72bd9b89..67824a0b 100644 --- a/react/CategoryBlock.tsx +++ b/react/CategoryBlock.tsx @@ -37,6 +37,8 @@ const CategoryBlock: FunctionComponent = ({ categoryItems: {}, quantitySelected: {}, defaultSeller: {}, + // Signed price of the selected seller, by SKU, as returned by the search + priceTokens: {}, // All the items with their respective units unitMultiplierList: {}, }) @@ -52,6 +54,7 @@ const CategoryBlock: FunctionComponent = ({ categoryItems, quantitySelected, defaultSeller, + priceTokens, unitMultiplierList, } = state @@ -274,6 +277,8 @@ const CategoryBlock: FunctionComponent = ({ if (skus?.length) { const items = skus.map((item: any) => { + const priceToken = priceTokens[item] + return { id: parseInt(item, 10), quantity: calculateDivisible( @@ -281,6 +286,7 @@ const CategoryBlock: FunctionComponent = ({ item ), seller: defaultSeller[item], + ...(priceToken ? { priceToken } : {}), } }) @@ -373,19 +379,34 @@ const CategoryBlock: FunctionComponent = ({ newQtd[content.itemId] = e.target.value const newSeller = defaultSeller + const newPriceTokens = priceTokens const seller = content.sellers.find((s: any) => { return s.sellerDefault === true }) - newSeller[content.itemId] = + const sellerId = seller?.sellerId || (content.sellers.length ? content.sellers[0].sellerId : '1') + // the token has to come from the very seller sent + // on addToCart, since it signs that seller's price + const selectedSeller = content.sellers.find( + (s: any) => { + return s.sellerId === sellerId + } + ) + + newSeller[content.itemId] = sellerId + newPriceTokens[content.itemId] = + selectedSeller?.commertialOffer?.PriceToken ?? + null + _setState({ quantitySelected: newQtd, defaultSeller: newSeller, + priceTokens: newPriceTokens, }) }} onBlur={() => { diff --git a/react/queries/product.gql b/react/queries/product.gql index b826f81b..630586e6 100644 --- a/react/queries/product.gql +++ b/react/queries/product.gql @@ -10,6 +10,9 @@ query Product($skuId: ID!) { sellerId sellerName sellerDefault + commertialOffer { + PriceToken + } } referenceId { Key diff --git a/react/queries/productsByCategory.gql b/react/queries/productsByCategory.gql index a85c83ec..21e98df2 100644 --- a/react/queries/productsByCategory.gql +++ b/react/queries/productsByCategory.gql @@ -10,6 +10,9 @@ query SearchByCategory($selectedFacets: [SelectedFacetInput]) { sellerId sellerName sellerDefault + commertialOffer { + PriceToken + } } images(quantity: 1) { imageUrl From 88e1d4386f09684138ef5b375421c5e07ba0a59f Mon Sep 17 00:00:00 2001 From: Wender Lima Date: Wed, 5 Aug 2026 15:31:05 -0400 Subject: [PATCH 2/3] [B2BTEAM-3732] Read priceToken in camelCase from search-graphql The raw Catalog Search API exposes the field as PriceToken, but search-graphql/search-resolver expose it as priceToken. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 2 +- react/AutocompleteBlock.tsx | 4 ++-- react/CategoryBlock.tsx | 2 +- react/queries/product.gql | 2 +- react/queries/productsByCategory.gql | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1532783..8c99beaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Fetch `commertialOffer.PriceToken` in the product queries and forward it as `priceToken` on the `addToCart` payload of `AutocompleteBlock` and `CategoryBlock`, so the Checkout can close the cart with the signed price while the Pricing is unavailable (Pricing Fallback V2) +- Fetch `commertialOffer.priceToken` in the product queries and forward it as `priceToken` on the `addToCart` payload of `AutocompleteBlock` and `CategoryBlock`, so the Checkout can close the cart with the signed price while the Pricing is unavailable (Pricing Fallback V2) ## [3.16.8] - 2026-06-29 diff --git a/react/AutocompleteBlock.tsx b/react/AutocompleteBlock.tsx index c2a49437..8fa9388c 100644 --- a/react/AutocompleteBlock.tsx +++ b/react/AutocompleteBlock.tsx @@ -197,7 +197,7 @@ const AutocompleteBlock: FunctionComponent = ({ // Signed price from the search response, forwarded on addToCart so the // Checkout can close the cart even while the Pricing is unavailable - const priceToken = defaultSeller?.commertialOffer?.PriceToken ?? null + const priceToken = defaultSeller?.commertialOffer?.priceToken ?? null let multiplier = 1 @@ -231,7 +231,7 @@ const AutocompleteBlock: FunctionComponent = ({ const newSelected = { ...selectedItem, seller: defaultSeller.sellerId, - priceToken: defaultSeller.commertialOffer?.PriceToken ?? null, + priceToken: defaultSeller.commertialOffer?.priceToken ?? null, value, } diff --git a/react/CategoryBlock.tsx b/react/CategoryBlock.tsx index 67824a0b..6b0aab41 100644 --- a/react/CategoryBlock.tsx +++ b/react/CategoryBlock.tsx @@ -400,7 +400,7 @@ const CategoryBlock: FunctionComponent = ({ newSeller[content.itemId] = sellerId newPriceTokens[content.itemId] = - selectedSeller?.commertialOffer?.PriceToken ?? + selectedSeller?.commertialOffer?.priceToken ?? null _setState({ diff --git a/react/queries/product.gql b/react/queries/product.gql index 630586e6..ddb51817 100644 --- a/react/queries/product.gql +++ b/react/queries/product.gql @@ -11,7 +11,7 @@ query Product($skuId: ID!) { sellerName sellerDefault commertialOffer { - PriceToken + priceToken } } referenceId { diff --git a/react/queries/productsByCategory.gql b/react/queries/productsByCategory.gql index 21e98df2..345fa13c 100644 --- a/react/queries/productsByCategory.gql +++ b/react/queries/productsByCategory.gql @@ -11,7 +11,7 @@ query SearchByCategory($selectedFacets: [SelectedFacetInput]) { sellerName sellerDefault commertialOffer { - PriceToken + priceToken } } images(quantity: 1) { From 07484af5263a2153a144aa81971a32a21f653623 Mon Sep 17 00:00:00 2001 From: Wender Lima Date: Thu, 6 Aug 2026 14:37:32 -0400 Subject: [PATCH 3/3] chore: ignore Claude Code local files .claude/ holds per-developer Claude Code settings (settings.local.json), which should not be versioned. Co-Authored-By: Claude Opus 5 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 15e85d3e..34ff5c91 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ node/coverage/* ### VS Code ### *.vscode +### Claude Code ### +.claude/ + ### OSX ### .DS_Store .AppleDouble