Skip to content

Commit 69e08f9

Browse files
authored
Merge pull request #49 from klaviyo/202507_ECOS-10765
Configurable Variation Group Sync + API revision bump
2 parents d0292cc + 31d77c7 commit 69e08f9

File tree

16 files changed

+146
-70
lines changed

16 files changed

+146
-70
lines changed

CHANGELOG.md

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

13+
### [25.7.0] - 25-07-29
14+
15+
#### Added
16+
- Site preference to optionally sync variation group identifiers instead of variation base product ids on events. This feature requires collaboration with the Klaviyo support team, as there are Klaviyo integration configurations that also must be set internally.
17+
- Updates to Klaviyo API revision 2025-07-15. See https://developers.klaviyo.com/en/docs/changelog_
18+
1319
### [25.4.0] - 25-04-17
1420

1521
#### Added
@@ -101,7 +107,8 @@ bumped for multiple releases during one month.
101107
<!-- END RELEASE NOTES -->
102108

103109
<!-- BEGIN LINKS -->
104-
[Unreleased]: https://github.com/klaviyo/SFCC_Klaviyo/compare/25.4.0...HEAD
110+
[Unreleased]: https://github.com/klaviyo/SFCC_Klaviyo/compare/25.7.0...HEAD
111+
[25.7.0]: https://github.com/klaviyo/SFCC_Klaviyo/compare/25.4.0...25.7.0
105112
[25.4.0]: https://github.com/klaviyo/SFCC_Klaviyo/compare/24.9.0...25.4.0
106113
[24.9.0]: https://github.com/klaviyo/SFCC_Klaviyo/compare/24.1.0...24.9.0
107114
[24.1.0]: https://github.com/klaviyo/SFCC_Klaviyo/compare/23.7.0...24.1.0

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function getData(basket) {
3333
var lineItem = basketItems[itemIndex];
3434
var currentProductID = lineItem.productID;
3535
var basketProduct = ProductMgr.getProduct(currentProductID);
36+
3637
if (!basketProduct) {
3738
throw new Error('Product with ID [' + currentProductID + '] not found');
3839
}
@@ -67,10 +68,11 @@ function getData(basket) {
6768
primaryCategory : primaryCategory
6869
};
6970

70-
if (!basketProduct.master && 'masterProduct' in basketProduct) {
71-
currentLineItem.masterProductID = basketProduct.masterProduct.ID;
71+
if (basketProduct.variant) {
72+
currentLineItem.masterProductID = klaviyoUtils.getParentProductId(basketProduct);
7273
}
7374

75+
7476
var priceData = klaviyoUtils.priceCheck(lineItem, basketProduct);
7577
currentLineItem.price = priceData.purchasePrice;
7678
currentLineItem.priceValue = priceData.purchasePriceValue;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ function getData(order) {
131131
'Product Image URL' : KLImageSize ? productDetail.getImage(KLImageSize).getAbsURL().toString() : null
132132
};
133133

134-
if (!productDetail.master && 'masterProduct' in productDetail) {
135-
currentLineItem['Master Product ID'] = productDetail.masterProduct.ID;
134+
if (productDetail.variant) {
135+
currentLineItem['Master Product ID'] = klaviyoUtils.getParentProductId(productDetail);
136136
}
137137

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ function getData(currentBasket) {
4545
if (currentProductID != null && !empty(basketProduct) && basketProduct.getPriceModel().getPrice().value > 0) {
4646
var productObj = prepareProductObj(lineItem, basketProduct, currentProductID);
4747

48-
if (!basketProduct.master && 'masterProduct' in basketProduct) {
49-
productObj['Master Product ID'] = basketProduct.masterProduct.ID;
48+
if (basketProduct.variant) {
49+
productObj['Master Product ID'] = klaviyoUtils.getParentProductId(basketProduct);
5050
}
5151

5252
if (options && options.length) {

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

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

25+
data['Product ID'] = klaviyoUtils.getParentProductId(product);
26+
27+
if (!product.master && 'masterProduct' in product) {
28+
// this is very unlikely to happen because PDPs are not typically used for variants
29+
data['Master Product ID'] = product.masterProduct.ID;
30+
}
31+
2532
var prices = require('*/cartridge/scripts/klaviyo/viewedProductHelpers.js').getProductPrices(product);
2633

2734
klaviyoUtils.setSiteIdAndIntegrationInfo(data, siteId);
28-
data['Product ID'] = product.ID;
2935
data['Product Name'] = product.name;
3036
data['Product Page URL'] = URLUtils.https('Product-Show', 'pid', product.ID).toString();
3137
data['Product Image URL'] = product.getImage(KLImageSize).getAbsURL().toString();
@@ -37,10 +43,6 @@ function getData(productID) {
3743
data['value'] = prices.price;
3844
data['value_currency'] = session.getCurrency().getCurrencyCode();
3945

40-
if (!product.master && 'masterProduct' in product) {
41-
data['Master Product ID'] = product.masterProduct.ID;
42-
}
43-
4446
var categories = [];
4547
var catProduct = (product.variant) ? product.masterProduct : product;
4648
for (var i = 0, len = catProduct.categoryAssignments.length; i < len; i++) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ var Site = require('dw/system/Site');
1010
* @returns {string} API version in YYYY-MM-DD format
1111
*/
1212
function getApiVersion() {
13-
return '2025-04-15';
13+
return '2025-07-15';
1414
}
1515

1616
/**
1717
* Returns the User-Agent string used in the X-Klaviyo-User-Agent header
1818
* @returns {string} User-Agent string
1919
*/
2020
function getUserAgent() {
21-
return 'sfcc-klaviyo/25.4.0';
21+
return 'sfcc-klaviyo/25.7.0';
2222
}
2323

2424
// HTTP Services

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,38 @@ 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.
86+
// this site preference is ONLY to be used in collaboration with Klaviyo support, because there is additional configuration required during integration setup in Klaviyo.
87+
// 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) {
89+
if (!product) {
90+
return null;
91+
}
92+
93+
var useVariationGroup = Site.getCurrent().getCustomPreferenceValue('use_variation_group_id') || false;
94+
95+
if (useVariationGroup) {
96+
// Return variation group ID when preference is enabled
97+
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;
103+
}
104+
// Fallback for other product types (bundle, set, etc.) or items w/o variations
105+
return product.ID;
106+
}
107+
108+
// Return master product ID for variants when preference is disabled (the default)
109+
if (!product.master && 'masterProduct' in product) {
110+
return product.masterProduct.ID;
111+
}
112+
113+
// Return product ID for master products or standalone items
114+
return product.ID;
115+
}
116+
85117
// 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.
86118
// This helper accomodates products that may have been configured with or feature multiple options by returning an array of each selected product option as its own optionObj.
87119
function captureProductOptions(prodOptions) {
@@ -405,6 +437,7 @@ module.exports = {
405437
getProfileInfo : getProfileInfo,
406438
prepareDebugData : prepareDebugData,
407439
dedupeArray : dedupeArray,
440+
getParentProductId : getParentProductId,
408441
captureProductOptions : captureProductOptions,
409442
captureProductBundles : captureProductBundles,
410443
captureBonusProduct : captureBonusProduct,

metadata.zip

294 Bytes
Binary file not shown.

test/src/tests/e2e/sfra/checkout/checkout-tests.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ test.describe('Test Klaviyo started checkout event', () => {
6363
expect(eventData.external_catalog_id).toBe(KLAVIYO_E2E_TEST_SITE_ID);
6464
expect(eventData.integration_key).toBe(KLAVIYO_DEMANDWARE_INTEGRATION_KEY);
6565
expect(eventData['Item Count']).toBe(1);
66-
expect(eventData.line_items[0]['Product Name']).toBe('Incase');
66+
expect(eventData.line_items[0]['Product Name']).toBe('Bootleg Trouser');
6767
expect(eventData['$email']).toBe(email);
6868
});
6969
});
@@ -97,8 +97,8 @@ test.describe('Test Klaviyo add to cart event', () => {
9797
expect(eventData.integration_key).toBe(KLAVIYO_DEMANDWARE_INTEGRATION_KEY);
9898

9999
expect(eventData.itemCount).toBe(1);
100-
expect(eventData.lineItems[0].productName).toBe('Incase');
101-
expect(eventData.productAddedToCart.productName).toBe('Incase');
100+
expect(eventData.lineItems[0].productName).toBe('Bootleg Trouser');
101+
expect(eventData.productAddedToCart.productName).toBe('Bootleg Trouser');
102102
});
103103
});
104104

@@ -115,7 +115,7 @@ test.describe('Test Klaviyo order confirmation event', () => {
115115
await checkoutPage.fillShippingForm(testData);
116116
await checkoutPage.submitShippingForm();
117117
await checkoutPage.fillPaymentForm(paymentData);
118-
await page.waitForTimeout(3000);
118+
await page.waitForTimeout(10000);
119119
expect(await page.innerText('h1.page-title')).toBe('Thank You');
120120

121121
const events = await checkEventWithRetry(email, KLAVIYO_METRIC_ID_ORDER_CONFIRMATION);
@@ -135,7 +135,7 @@ test.describe('Test Klaviyo order confirmation event', () => {
135135

136136
expect(eventData['Item Count']).toBe(1);
137137
expect(eventData.product_line_items.length).toBe(1);
138-
expect(eventData.product_line_items[0]['Product Name']).toBe('Incase');
138+
expect(eventData.product_line_items[0]['Product Name']).toBe('Bootleg Trouser');
139139
});
140140
});
141141

test/src/tests/e2e/sfra/product/product-tests.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test.describe('Test Klaviyo viewed product event', () => {
5252
expect(metricData.attributes.integration.key).toBe('api');
5353

5454
// Validate event data
55-
expect(eventData['Product Name']).toBe('Incase');
55+
expect(eventData['Product Name']).toBe('Bootleg Trouser');
5656
expect(eventData.external_catalog_id).toBe(KLAVIYO_E2E_TEST_SITE_ID);
5757
expect(eventData.integration_key).toBe(KLAVIYO_DEMANDWARE_INTEGRATION_KEY);
5858
});

0 commit comments

Comments
 (0)