Skip to content

Commit f61bfc9

Browse files
authored
Merge pull request #2558 from SalesforceCommerceCloud/bendvc/W-18647820_fix-einstein-add-to-cart
[Einstein] Fix `addToCart` payload @@W-18647820@@
2 parents ccf10b3 + 1860c67 commit f61bfc9

File tree

4 files changed

+189
-39
lines changed

4 files changed

+189
-39
lines changed

packages/template-retail-react-app/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## v7.0.0-dev.0 (May 20, 2025)
22

33
- Improved the layout of product tiles in product scroll and product list [#2446](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2446)
4-
- Updated 6 new languagues [#2495](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2495)
4+
- Updated 6 new languages [#2495](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2495)
5+
- Fix Einstein event tracking for `addToCart` event [#2558](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2558)
56

67
## v6.1.0 (May 22, 2025)
78

packages/template-retail-react-app/app/hooks/use-einstein.js

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,42 +56,57 @@ export class EinsteinAPI {
5656
* Given a product or item source, returns the product data that Einstein requires
5757
*/
5858
_constructEinsteinProduct(product) {
59-
if (product.type && (product.type.master || product.type.variant)) {
60-
// handle variants for PDP / viewProduct
61-
// Assumes product is a Product object from SCAPI Shopper-Products:
62-
// https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-products?meta=type%3AProduct
59+
// Handle variants for PDP / viewProduct
60+
// Assumes product is a Product object from SCAPI Shopper-Products:
61+
// https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-products?meta=type%3AProduct
62+
if (product.type) {
63+
// In case of variant, send the "sku" attribute
64+
if (product.type.variant) {
65+
return {
66+
id: product.master.masterId,
67+
price: product.price,
68+
sku: product.id
69+
}
70+
}
71+
72+
// In case of variation group, send the "altId" and "type" attributes
73+
if (product.type.variationGroup) {
74+
return {
75+
altId: product.id,
76+
id: product.master.masterId,
77+
price: product.price,
78+
sku: product.id,
79+
type: 'vgroup'
80+
}
81+
}
82+
83+
// Handle non-variant products, like master, set, bundle, item.
84+
// This code follows the implementation in the plugin_einstein_api.
85+
// https://github.com/SalesforceCommerceCloud/plugin_einstein_api/blob/c8168d5b8e2e34bfb9413da73969d59b0f3adabd/plugin_einstein_api/cartridge/scripts/helpers/RecommendationsHelper.js#L315
6386
return {
64-
id: product.master.masterId,
65-
sku: product.id,
66-
altId: '',
67-
altIdType: ''
87+
id: product.id,
88+
price: product.price
6889
}
69-
} else if (
70-
product.productType &&
71-
(product.productType.master ||
72-
product.productType.variant ||
73-
product.productType.set ||
74-
product.productType.bundle ||
75-
product.productType.variationGroup ||
76-
product.productType.item)
77-
) {
78-
// handle variants & sets for PLP / viewCategory & viewSearch
79-
// Assumes product is a ProductSearchHit from SCAPI Shopper-Search:
80-
// https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-search?meta=type%3AProductSearchHit
90+
}
91+
92+
// Handle variants & sets for PLP / viewCategory & viewSearch
93+
// Assumes product is a ProductSearchHit from SCAPI Shopper-Search:
94+
// https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-search?meta=type%3AProductSearchHit
95+
if (product.hitType) {
8196
return {
8297
id: product.productId,
8398
sku: product.productId, //TODO: Should we switch this to product.representedProduct.id once we allow non-master products in search results?
8499
altId: '',
85100
altIdType: ''
86101
}
87-
} else {
88-
// handles non-variants
89-
return {
90-
id: product.id,
91-
sku: '',
92-
altId: '',
93-
altIdType: ''
94-
}
102+
}
103+
104+
// handles non-variants
105+
return {
106+
id: product.id,
107+
sku: '',
108+
altId: '',
109+
altIdType: ''
95110
}
96111
}
97112

@@ -100,13 +115,40 @@ export class EinsteinAPI {
100115
*
101116
* Assumes item is a ProductItemfrom SCAPI Shopper-Baskets:
102117
* https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=type%3AProductItem
118+
*
119+
* This code follows the implementation in the plugin_einstein_api.
120+
* https://github.com/SalesforceCommerceCloud/plugin_einstein_api/blob/c8168d5b8e2e34bfb9413da73969d59b0f3adabd/plugin_einstein_api/cartridge/scripts/helpers/RecommendationsHelper.js#L315
103121
*/
104122
_constructEinsteinItem(item) {
123+
const {product, productId, price, quantity} = item
124+
125+
// In case of variant, send the "sku" attribute
126+
if (product?.type?.variant) {
127+
return {
128+
id: product.master.masterId,
129+
quantity: quantity,
130+
price: price,
131+
sku: product.id
132+
}
133+
}
134+
135+
// In case of variation group, send the "altId" and "type" attributes
136+
if (product?.type?.variationGroup) {
137+
return {
138+
id: product.master.masterId,
139+
quantity: quantity,
140+
price: price,
141+
sku: product.id,
142+
type: 'vgroup',
143+
altId: product.id
144+
}
145+
}
146+
147+
// Otherwise send default attributes.
105148
return {
106-
id: item.productId,
107-
sku: '',
108-
price: item.price,
109-
quantity: item.quantity
149+
id: productId,
150+
price: price,
151+
quantity: quantity
110152
}
111153
}
112154

packages/template-retail-react-app/app/hooks/use-einstein.test.js

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,78 @@ afterAll(() => {
3535
})
3636

3737
describe('EinsteinAPI', () => {
38+
test('_constructEinsteinProduct handles variationGroup product type', () => {
39+
const variationGroupProduct = {
40+
id: 'test-variation-group-id',
41+
price: 99.99,
42+
type: {
43+
variationGroup: true
44+
},
45+
master: {
46+
masterId: 'master-product-id'
47+
}
48+
}
49+
50+
const result = einsteinApi._constructEinsteinProduct(variationGroupProduct)
51+
52+
expect(result).toEqual({
53+
altId: 'test-variation-group-id',
54+
id: 'master-product-id',
55+
price: 99.99,
56+
sku: 'test-variation-group-id',
57+
type: 'vgroup'
58+
})
59+
})
60+
61+
test('_constructEinsteinProduct handles variant product type', () => {
62+
const variantProduct = {
63+
id: 'test-variant-id',
64+
price: 99.99,
65+
type: {
66+
variant: true
67+
},
68+
master: {
69+
masterId: 'master-product-id'
70+
}
71+
}
72+
73+
const result = einsteinApi._constructEinsteinProduct(variantProduct)
74+
75+
expect(result).toEqual({
76+
id: 'master-product-id',
77+
price: 99.99,
78+
sku: 'test-variant-id'
79+
})
80+
})
81+
82+
test('_constructEinsteinItem handles variationGroup product type', () => {
83+
const variationGroupItem = {
84+
product: {
85+
id: 'test-variation-group-id',
86+
type: {
87+
variationGroup: true
88+
},
89+
master: {
90+
masterId: 'master-product-id'
91+
}
92+
},
93+
productId: 'test-variation-group-id',
94+
price: 99.99,
95+
quantity: 2
96+
}
97+
98+
const result = einsteinApi._constructEinsteinItem(variationGroupItem)
99+
100+
expect(result).toEqual({
101+
id: 'master-product-id',
102+
quantity: 2,
103+
price: 99.99,
104+
sku: 'test-variation-group-id',
105+
type: 'vgroup',
106+
altId: 'test-variation-group-id'
107+
})
108+
})
109+
38110
test('viewProduct sends expected api request', async () => {
39111
await einsteinApi.sendViewProduct(mockProduct, {cookieId: 'test-usid'})
40112

@@ -46,7 +118,7 @@ describe('EinsteinAPI', () => {
46118
'Content-Type': 'application/json',
47119
'x-cq-client-id': 'test-id'
48120
},
49-
body: '{"product":{"id":"56736828M","sku":"56736828M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
121+
body: '{"product":{"id":"56736828M","price":155},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
50122
}
51123
)
52124
})
@@ -158,12 +230,12 @@ describe('EinsteinAPI', () => {
158230
'Content-Type': 'application/json',
159231
'x-cq-client-id': 'test-id'
160232
},
161-
body: '{"products":[{"id":"682875719029M","sku":"","price":29.99,"quantity":1}],"amount":29.99,"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
233+
body: '{"products":[{"id":"682875719029M","price":29.99,"quantity":1}],"amount":29.99,"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
162234
}
163235
)
164236
})
165237

166-
test('checkouStep sends expected api request', async () => {
238+
test('checkoutStep sends expected api request', async () => {
167239
const checkoutStepName = 'CheckoutStep'
168240
const checkoutStep = 0
169241
await einsteinApi.sendCheckoutStep(checkoutStepName, checkoutStep, mockBasket, {
@@ -192,7 +264,7 @@ describe('EinsteinAPI', () => {
192264
'Content-Type': 'application/json',
193265
'x-cq-client-id': 'test-id'
194266
},
195-
body: '{"products":[{"id":"883360544021M","sku":"","price":155,"quantity":1}],"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
267+
body: '{"products":[{"id":"883360544021M","price":155,"quantity":1}],"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
196268
}
197269
)
198270
})
@@ -209,7 +281,7 @@ describe('EinsteinAPI', () => {
209281
'Content-Type': 'application/json',
210282
'x-cq-client-id': 'test-id'
211283
},
212-
body: '{"recommenderName":"testRecommender","__recoUUID":"883360544021M","product":{"id":"56736828M","sku":"56736828M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
284+
body: '{"recommenderName":"testRecommender","__recoUUID":"883360544021M","product":{"id":"56736828M","price":155},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
213285
}
214286
)
215287
})
@@ -249,4 +321,31 @@ describe('EinsteinAPI', () => {
249321
}
250322
)
251323
})
324+
325+
test('sendViewProduct handles variationGroup product type', async () => {
326+
const variationGroupProduct = {
327+
id: 'test-variation-group-id',
328+
price: 99.99,
329+
type: {
330+
variationGroup: true
331+
},
332+
master: {
333+
masterId: 'master-product-id'
334+
}
335+
}
336+
337+
await einsteinApi.sendViewProduct(variationGroupProduct, {cookieId: 'test-usid'})
338+
339+
expect(fetch).toHaveBeenCalledWith(
340+
'http://localhost/test-path/v3/activities/test-site-id/viewProduct',
341+
{
342+
method: 'POST',
343+
headers: {
344+
'Content-Type': 'application/json',
345+
'x-cq-client-id': 'test-id'
346+
},
347+
body: '{"product":{"altId":"test-variation-group-id","id":"master-product-id","price":99.99,"sku":"test-variation-group-id","type":"vgroup"},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
348+
}
349+
)
350+
})
252351
})

packages/template-retail-react-app/app/pages/product-detail/index.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,15 @@ const ProductDetail = () => {
304304

305305
await addItemToNewOrExistingBasket(productItems)
306306

307-
einstein.sendAddToCart(productItems)
307+
const productItemsForEinstein = productSelectionValues.map(
308+
({product, variant, quantity}) => ({
309+
product,
310+
productId: variant.productId,
311+
price: variant.price,
312+
quantity
313+
})
314+
)
315+
einstein.sendAddToCart(productItemsForEinstein)
308316

309317
// If the items were successfully added, set the return value to be used
310318
// by the add to cart modal.

0 commit comments

Comments
 (0)