Skip to content

Commit 3e2d3ba

Browse files
authored
Patch fix for variation group support, updates unique_id for order confirmation event
* refactor getParentProductId to getParentProduct * fixes bug to get categories from return val of getParentProduct instead of deferring to the master product * adds unique_id for order confirmation events, to prevent dupe events for the same order number.
1 parent 69e08f9 commit 3e2d3ba

File tree

12 files changed

+105
-61
lines changed

12 files changed

+105
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ storefront-reference-architecture
88
.vscode
99
.prettierrc
1010
.env
11+
.python-version
1112

1213
# Playwright
1314
/test-results/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ bumped for multiple releases during one month.
1010
<!-- BEGIN RELEASE NOTES -->
1111
### [Unreleased]
1212

13+
#### Fixed
14+
- Copies the $event_id field up to the top-level `unique_id` field on Order Confirmation events to prevent duplicate events being created on page reload.
15+
- Updates event data extraction logic to use the parent product returned from `getParentProduct` to hydrate categories on events.
16+
1317
### [25.7.0] - 25-07-29
1418

1519
#### Added

cartridges/int_klaviyo_core/cartridge/scripts/klaviyo/eventData/addedToCart.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,30 @@ function getData(basket) {
3333
var lineItem = basketItems[itemIndex];
3434
var currentProductID = lineItem.productID;
3535
var basketProduct = ProductMgr.getProduct(currentProductID);
36+
var parentProduct = klaviyoUtils.getParentProduct(basketProduct);
3637

3738
if (!basketProduct) {
3839
throw new Error('Product with ID [' + currentProductID + '] not found');
3940
}
4041

4142
if (currentProductID != null && !empty(basketProduct) && basketProduct.getPriceModel().getPrice().value > 0) {
4243
var primaryCategory;
43-
var selectedOptions;
44-
if (basketProduct.variant) {
45-
primaryCategory = (basketProduct.masterProduct.primaryCategory) ? basketProduct.masterProduct.primaryCategory.displayName : '';
46-
} else {
47-
primaryCategory = (basketProduct.primaryCategory) ? basketProduct.primaryCategory.displayName : '';
44+
var categories = [];
45+
if (parentProduct) {
46+
primaryCategory = parentProduct.primaryCategory ? parentProduct.primaryCategory.displayName : '';
47+
if (parentProduct.categoryAssignments) {
48+
for (var i = 0, len = parentProduct.categoryAssignments.length; i < len; i++) {
49+
categories.push(parentProduct.categoryAssignments[i].category.displayName);
50+
}
51+
}
4852
}
53+
var selectedOptions;
54+
4955
var imageSizeOfProduct = null;
5056
if (KLImageSize && basketProduct.getImage(KLImageSize)) {
5157
imageSizeOfProduct = basketProduct.getImage(KLImageSize).getAbsURL().toString();
5258
}
5359

54-
var categories = [];
55-
var catProduct = (basketProduct.variant) ? basketProduct.masterProduct : basketProduct; // from orig klav code, always use master for finding cats
56-
for (var i = 0, len = catProduct.categoryAssignments.length; i < len; i++) {
57-
categories.push(catProduct.categoryAssignments[i].category.displayName);
58-
}
5960

6061
var currentLineItem = {
6162
productID : currentProductID,
@@ -68,8 +69,8 @@ function getData(basket) {
6869
primaryCategory : primaryCategory
6970
};
7071

71-
if (basketProduct.variant) {
72-
currentLineItem.masterProductID = klaviyoUtils.getParentProductId(basketProduct);
72+
if (parentProduct) {
73+
currentLineItem.masterProductID = parentProduct.ID;
7374
}
7475

7576

cartridges/int_klaviyo_core/cartridge/scripts/klaviyo/eventData/orderConfirmation.js

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ function getData(order) {
7777
throw new Error('Product with ID [' + lineItemProduct.ID + '] not found');
7878
}
7979

80+
var parentProduct = klaviyoUtils.getParentProduct(productDetail);
81+
8082
// Variation values
8183
var variationValues = '';
8284
if (productDetail.isVariant()) {
@@ -95,21 +97,16 @@ function getData(order) {
9597

9698
items.push(productLineItem.productID);
9799
itemCount += productLineItem.quantity.value;
98-
var allCategories;
99-
if (productDetail.variant) {
100-
if (productDetail.masterProduct.getPrimaryCategory()) {
101-
itemPrimaryCategories.push(
102-
productDetail.masterProduct.getPrimaryCategory().displayName
103-
);
104-
}
105-
allCategories = productDetail.masterProduct.getAllCategories();
106-
} else {
107-
if (productDetail.getPrimaryCategory()) {
108-
itemPrimaryCategories.push(
109-
productDetail.getPrimaryCategory().displayName
110-
);
100+
101+
var primaryCategory = '';
102+
var allCategories = [];
103+
if (parentProduct) {
104+
// parentProduct can only be null if the klaviyo_use_variation_group_id site preference is enabled and the product has no variation groups
105+
primaryCategory = parentProduct.getPrimaryCategory() ? parentProduct.getPrimaryCategory().displayName : null;
106+
if (primaryCategory) {
107+
itemPrimaryCategories.push(primaryCategory);
111108
}
112-
allCategories = productDetail.getAllCategories();
109+
allCategories = parentProduct.getAllCategories();
113110
}
114111

115112
if (!empty(allCategories) && allCategories.length > 0) {
@@ -131,8 +128,8 @@ function getData(order) {
131128
'Product Image URL' : KLImageSize ? productDetail.getImage(KLImageSize).getAbsURL().toString() : null
132129
};
133130

134-
if (productDetail.variant) {
135-
currentLineItem['Master Product ID'] = klaviyoUtils.getParentProductId(productDetail);
131+
if (parentProduct) {
132+
currentLineItem['Master Product ID'] = parentProduct.ID;
136133
}
137134

138135
var priceData = klaviyoUtils.priceCheck(productLineItem, productDetail);

cartridges/int_klaviyo_core/cartridge/scripts/klaviyo/eventData/startedCheckout.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ function getData(currentBasket) {
3939
if (!basketProduct) {
4040
throw new Error('Product with ID [' + currentProductID + '] not found');
4141
}
42+
var parentProduct = klaviyoUtils.getParentProduct(basketProduct);
4243
var quantity = lineItem.quantityValue;
4344
var options = lineItem && lineItem.optionProductLineItems ? klaviyoUtils.captureProductOptions(lineItem.optionProductLineItems) : null;
4445

4546
if (currentProductID != null && !empty(basketProduct) && basketProduct.getPriceModel().getPrice().value > 0) {
46-
var productObj = prepareProductObj(lineItem, basketProduct, currentProductID);
47+
var productObj = prepareProductObj(lineItem, basketProduct, currentProductID, parentProduct);
4748

48-
if (basketProduct.variant) {
49-
productObj['Master Product ID'] = klaviyoUtils.getParentProductId(basketProduct);
49+
if (parentProduct) {
50+
productObj['Master Product ID'] = parentProduct.ID;
5051
}
5152

5253
if (options && options.length) {
@@ -81,7 +82,7 @@ function getData(currentBasket) {
8182
}
8283

8384

84-
function prepareProductObj(lineItem, basketProduct, currentProductID) {
85+
function prepareProductObj(lineItem, basketProduct, currentProductID, parentProduct) {
8586
var productObj = {};
8687
if (lineItem.bonusProductLineItem) {
8788
var bonusProduct = klaviyoUtils.captureBonusProduct(lineItem, basketProduct);
@@ -107,9 +108,11 @@ function prepareProductObj(lineItem, basketProduct, currentProductID) {
107108
productObj['Product Availability Model'] = basketProduct.availabilityModel.availability;
108109

109110
var categories = [];
110-
var catProduct = (basketProduct.variant) ? basketProduct.masterProduct : basketProduct;
111-
for (var i = 0, len = catProduct.categoryAssignments.length; i < len; i++) {
112-
categories.push(catProduct.categoryAssignments[i].category.displayName);
111+
if (parentProduct) {
112+
// parentProduct can only be null if the klaviyo_use_variation_group_id site preference is enabled and the product has no variation groups
113+
for (var i = 0, len = parentProduct.categoryAssignments.length; i < len; i++) {
114+
categories.push(parentProduct.categoryAssignments[i].category.displayName);
115+
}
113116
}
114117

115118
productObj.Categories = categories;

cartridges/int_klaviyo_core/cartridge/scripts/klaviyo/eventData/viewedProduct.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ function getData(productID) {
2222
throw new Error('Product with ID [' + productID + '] not found');
2323
}
2424

25-
data['Product ID'] = klaviyoUtils.getParentProductId(product);
25+
// use the configurable setting to see which product id should be used on the event
26+
var parentProduct = klaviyoUtils.getParentProduct(product);
27+
data['Product ID'] = parentProduct ? parentProduct.ID : null;
2628

2729
if (!product.master && 'masterProduct' in product) {
2830
// this is very unlikely to happen because PDPs are not typically used for variants
@@ -44,14 +46,16 @@ function getData(productID) {
4446
data['value_currency'] = session.getCurrency().getCurrencyCode();
4547

4648
var categories = [];
47-
var catProduct = (product.variant) ? product.masterProduct : product;
48-
for (var i = 0, len = catProduct.categoryAssignments.length; i < len; i++) {
49-
categories.push(catProduct.categoryAssignments[i].category.displayName);
49+
if (parentProduct) {
50+
for (var i = 0, len = parentProduct.categoryAssignments.length; i < len; i++) {
51+
categories.push(parentProduct.categoryAssignments[i].category.displayName);
52+
}
5053
}
54+
5155
categories = klaviyoUtils.dedupeArray(categories);
5256

5357
data['Categories'] = categories;
54-
data['Primary Category'] = !empty(catProduct.getPrimaryCategory()) ? catProduct.getPrimaryCategory().displayName : '';
58+
data['Primary Category'] = !empty(parentProduct.getPrimaryCategory()) ? parentProduct.getPrimaryCategory().displayName : '';
5559
} catch (e) {
5660
var logger = Logger.getLogger('Klaviyo', 'Klaviyo.core viewedProduct.js');
5761
logger.error('viewedProduct.getData() failed to create data object: ' + e.message + ' ' + e.stack);

cartridges/int_klaviyo_core/cartridge/scripts/klaviyo/utils.js

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,36 +82,58 @@ function dedupeArray(items) {
8282
}
8383

8484

85-
// this uses the use_variation_group_id site preference to determine if the base product ID should be the variation group ID or the master product ID.
85+
// this uses the klaviyo_use_variation_group_id site preference to determine if the base product ID should be the variation group ID or the master product ID.
8686
// this site preference is ONLY to be used in collaboration with Klaviyo support, because there is additional configuration required during integration setup in Klaviyo.
8787
// this is so that Klaviyo can correctly attribute events to the correct catalog item in Klaviyo, for use in reporting, product feeds, flows, etc.
88-
function getParentProductId(product) {
88+
function getParentProduct(product) {
8989
if (!product) {
9090
return null;
9191
}
9292

93-
var useVariationGroup = Site.getCurrent().getCustomPreferenceValue('use_variation_group_id') || false;
93+
var useVariationGroup = Site.getCurrent().getCustomPreferenceValue('klaviyo_use_variation_group_id') || false;
9494

9595
if (useVariationGroup) {
9696
// Return variation group ID when preference is enabled
97+
var variationGroups;
98+
var variants;
99+
97100
if (product.variant) {
98-
var productVariationModel = product.getVariationModel();
99-
var groups = productVariationModel.getVariationGroups();
100-
return groups.length > 0 ? groups[0].ID : null;
101-
} else if (product.variationGroups.length > 0) {
102-
return product.variationGroups[0].ID;
101+
var masterProduct = product.masterProduct;
102+
if (masterProduct.variationGroups.length > 0) {
103+
variationGroups = masterProduct.variationGroups;
104+
for (var i = 0; i < variationGroups.length; i++) {
105+
variants = variationGroups[i].variants;
106+
for (var j = 0; j < variants.length; j++) {
107+
var variant = variants[j];
108+
if (variant.ID === product.ID) {
109+
return variationGroups[i];
110+
}
111+
}
112+
}
113+
}
114+
return null;
115+
} else if (product.variationGroups && product.variationGroups.length > 0) {
116+
variationGroups = product.variationGroups;
117+
// product is not a variant, return first group id that has assigned variants
118+
for (var v = 0; v < variationGroups.length; v++) {
119+
variants = variationGroups[v].variants;
120+
if (variants.length > 0) {
121+
return variationGroups[v];
122+
}
123+
}
103124
}
125+
104126
// Fallback for other product types (bundle, set, etc.) or items w/o variations
105-
return product.ID;
127+
return product;
106128
}
107129

108130
// Return master product ID for variants when preference is disabled (the default)
109131
if (!product.master && 'masterProduct' in product) {
110-
return product.masterProduct.ID;
132+
return product.masterProduct;
111133
}
112134

113135
// Return product ID for master products or standalone items
114-
return product.ID;
136+
return product;
115137
}
116138

117139
// helper function to extract product options and return each selected option into an object with five keys: 'Line Item Text', 'Option ID' and 'Option Value ID', 'Option Price' and 'Option Price Value.
@@ -244,9 +266,10 @@ function trackEvent(exchangeID, data, event, customerEmail) {
244266
}
245267
};
246268

247-
// Extract value and value_currency as top-level fields
269+
// Extract value, value_currency and unique_id as top-level fields
248270
var value;
249271
var valueCurrency;
272+
var uniqueId;
250273

251274
if (data.value) {
252275
value = data.value;
@@ -258,6 +281,10 @@ function trackEvent(exchangeID, data, event, customerEmail) {
258281
}
259282
}
260283

284+
if (data.$event_id) {
285+
uniqueId = data.$event_id;
286+
}
287+
261288
// EVENT DATA
262289
var eventData = {
263290
data: {
@@ -268,6 +295,7 @@ function trackEvent(exchangeID, data, event, customerEmail) {
268295
properties : data,
269296
value: value,
270297
value_currency: valueCurrency,
298+
unique_id : uniqueId,
271299
time : (new Date()).toISOString()
272300
}
273301
}
@@ -437,7 +465,7 @@ module.exports = {
437465
getProfileInfo : getProfileInfo,
438466
prepareDebugData : prepareDebugData,
439467
dedupeArray : dedupeArray,
440-
getParentProductId : getParentProductId,
468+
getParentProduct : getParentProduct,
441469
captureProductOptions : captureProductOptions,
442470
captureProductBundles : captureProductBundles,
443471
captureBonusProduct : captureBonusProduct,

metadata.zip

-3 Bytes
Binary file not shown.

test/src/tests/unit/added-to-cart.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const setSiteIdAndIntegrationInfo = require('../mocks/util/setSiteIdAndIntegrati
1616
require('app-module-path').addPath(path.join(process.cwd(), '../cartridges'))
1717

1818
global.empty = sinon.stub()
19-
const getParentProductIdStub = sinon.stub()
19+
const getParentProductStub = sinon.stub()
2020

2121
const basketManagerMock = new BasketMgr(false)
2222
const currentBasket = basketManagerMock.getCurrentBasket()
@@ -45,7 +45,7 @@ const addedToCartEvent = proxyquire('int_klaviyo_core/cartridge/scripts/klaviyo/
4545
dedupeArray: () => {
4646
return ['Health']
4747
},
48-
getParentProductId: getParentProductIdStub.returns('NG3614270264406'),
48+
getParentProduct: getParentProductStub.returns({ID: 'NG3614270264406', categoryAssignments: [{category: {displayName: 'Health'}}], primaryCategory: {displayName: 'Skin Care'}}),
4949
setSiteIdAndIntegrationInfo: setSiteIdAndIntegrationInfo
5050
},
5151
})

test/src/tests/unit/order-confirmation.test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ global.session = {
4343
}
4444
}
4545
}
46-
const getParentProductIdStub = sinon.stub()
46+
const getParentProductStub = sinon.stub()
4747
const basketManagerMock = new BasketMgr(true)
4848

4949
const orderConfirmationEvent = proxyquire('int_klaviyo_core/cartridge/scripts/klaviyo/eventData/orderConfirmation.js', {
@@ -63,7 +63,11 @@ const orderConfirmationEvent = proxyquire('int_klaviyo_core/cartridge/scripts/kl
6363
return ['Skin Care']
6464
},
6565
setSiteIdAndIntegrationInfo: setSiteIdAndIntegrationInfo,
66-
getParentProductId: getParentProductIdStub.returns('NG3614270264406'),
66+
getParentProduct: getParentProductStub.returns({ID: 'NG3614270264406', categoryAssignments: [{category: {displayName: 'Health'}}], primaryCategory: {displayName: 'Skin Care'}, getPrimaryCategory: function() {
67+
return {displayName: 'Skin Care'}
68+
}, getAllCategories: function() {
69+
return [{displayName: 'Skin Care'}]
70+
}}),
6771
},
6872
})
6973

0 commit comments

Comments
 (0)