Skip to content

Commit 618df3e

Browse files
jaissica12rmi22186
authored andcommitted
feat: SDKE-482 support custom product attributes (#49)
1 parent f647d4b commit 618df3e

File tree

2 files changed

+147
-3
lines changed

2 files changed

+147
-3
lines changed

src/FacebookEventForwarder.js

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,14 @@
6060
constructor = function () {
6161
var self = this,
6262
isInitialized = false,
63-
reportingService = null;
63+
reportingService = null,
64+
settings,
65+
productAttributeMapping;
6466

6567
self.name = name;
6668

67-
function initForwarder(settings, service, testMode, trackerId, userAttributes, userIdentities) {
69+
function initForwarder(forwarderSettings, service, testMode, trackerId, userAttributes, userIdentities) {
70+
settings = forwarderSettings;
6871
reportingService = service;
6972

7073
SupportedCommerceTypes = [
@@ -107,6 +110,8 @@
107110
fbq.disablePushState = true;
108111
}
109112
fbq('init', settings.pixelId, visitorData);
113+
114+
loadMappings();
110115

111116
isInitialized = true;
112117

@@ -118,6 +123,12 @@
118123
}
119124
}
120125

126+
function loadMappings() {
127+
productAttributeMapping = settings.productAttributeMapping
128+
? JSON.parse(settings.productAttributeMapping.replace(/"/g, '"'))
129+
: [];
130+
}
131+
121132
function processEvent(event) {
122133
var reportEvent = false;
123134

@@ -212,7 +223,7 @@
212223

213224
}
214225
else if (event.ProductAction.ProductActionType == mParticle.ProductActionType.Checkout ||
215-
event.ProductAction.ProductActionType == mParticle.ProductActionType.Purchase) {
226+
event.ProductAction.ProductActionType == mParticle.ProductActionType.Purchase) {
216227

217228
eventName = event.ProductAction.ProductActionType == mParticle.ProductActionType.Checkout ? CHECKOUT_EVENT_NAME : PURCHASE_EVENT_NAME;
218229

@@ -227,6 +238,18 @@
227238
return sum;
228239
}, 0);
229240
params['num_items'] = num_items;
241+
242+
if (event.ProductAction.TransactionId) {
243+
params['order_id'] = event.ProductAction.TransactionId;
244+
}
245+
246+
// Build contents array for Purchase events
247+
if (event.ProductAction.ProductActionType == mParticle.ProductActionType.Purchase) {
248+
var contents = buildProductContents(event.ProductAction.ProductList);
249+
if (contents && contents.length > 0) {
250+
params['contents'] = contents;
251+
}
252+
}
230253
}
231254
else if (event.ProductAction.ProductActionType == mParticle.ProductActionType.RemoveFromCart) {
232255
eventName = REMOVE_FROM_CART_EVENT_NAME;
@@ -323,6 +346,60 @@
323346
return null;
324347
}
325348

349+
/**
350+
* Builds contents array for Facebook Pixel commerce events.
351+
* Creates a nested array of content items with product details.
352+
*
353+
* @param {Array} productList - Array of products from event.ProductAction.ProductList
354+
* @returns {Array} Array of content objects for Facebook Pixel
355+
*/
356+
function buildProductContents(productList) {
357+
if (!productList || productList.length === 0) {
358+
return [];
359+
}
360+
361+
return productList
362+
.filter(function(product) {
363+
return product && product.Sku;
364+
})
365+
.map(function(product) {
366+
var contentItem = {
367+
id: product.Sku,
368+
quantity: isNumeric(product.Quantity) ? product.Quantity : 1,
369+
name: product.Name,
370+
brand: product.Brand,
371+
category: product.Category,
372+
variant: product.Variant,
373+
item_price: isNumeric(product.Price) ? product.Price : null
374+
};
375+
376+
// Apply configured mappings to custom attributes
377+
productAttributeMapping.forEach(function(productMapping) {
378+
if (!isobject(productMapping) || !productMapping.map || !productMapping.value) {
379+
return;
380+
}
381+
382+
var sourceField = productMapping.map;
383+
var facebookFieldName = productMapping.value;
384+
var value = null;
385+
386+
// Check for Product level field first
387+
if (product.hasOwnProperty(sourceField)) {
388+
value = product[sourceField];
389+
}
390+
// then check for Product.Attributes level field
391+
else if (product.Attributes && product.Attributes[sourceField]) {
392+
value = product.Attributes[sourceField];
393+
}
394+
395+
if (value !== null && value !== undefined) {
396+
contentItem[facebookFieldName] = value;
397+
}
398+
});
399+
return contentItem;
400+
});
401+
}
402+
326403
// https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events#event-deduplication-options
327404
function createEventId(event) {
328405
return {

test/tests.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ describe('Facebook Forwarder', function () {
481481
window.fbqObj.params.should.have.property('content_name', 'eCommerce - AddToCart');
482482
window.fbqObj.params.should.have.property('content_ids', ['12345']);
483483
window.fbqObj.eventData.should.have.property('eventID', SOURCE_MESSAGE_ID);
484+
window.fbqObj.params.should.not.have.property('contents');
484485

485486
done();
486487
});
@@ -857,5 +858,71 @@ describe('Facebook Forwarder', function () {
857858
window.fbqObj.should.have.property('trackCalled', false);
858859
done();
859860
});
861+
862+
it('should build contents array with mapped attributes for Purchase events', function (done) {
863+
// Initialize with product attribute mapping
864+
mParticle.forwarder.init({
865+
pixelCode: 'test-pixel-code',
866+
"productAttributeMapping":"[{"jsmap":"3373707","map":"Name","maptype":"ProductAttributeSelector.Name","value":"custom_name"},{"jsmap":"93997959","map":"Brand","maptype":"ProductAttributeSelector.Name","value":"custom_brand"},{"jsmap":"106934601","map":"Price","maptype":"ProductAttributeSelector.Name","value":"custom_price"},{"jsmap":"50511102","map":"Category","maptype":"ProductAttributeSelector.Name","value":"custom_category"},{"jsmap":"94842723","map":"category","maptype":"ProductAttributeSelector.Name","value":"custom_attribute_category"}]"
867+
}, reportService.cb, true);
868+
869+
mParticle.forwarder.process({
870+
EventName: 'eCommerce - Purchase',
871+
EventDataType: MessageType.Commerce,
872+
ProductAction: {
873+
ProductActionType: ProductActionType.Purchase,
874+
ProductList: [
875+
{
876+
Sku: 'sku-12',
877+
Name: 'iPhone',
878+
Brand: 'Apple',
879+
Category: 'electronics',
880+
Variant: 'blue',
881+
Price: 1000.99,
882+
Quantity: 1,
883+
Attributes: {
884+
category: 'phones'
885+
}
886+
},
887+
{
888+
Sku: 'sku-34',
889+
Name: 'Watch',
890+
Brand: 'Samsung',
891+
Price: 450.99,
892+
Quantity: 2
893+
}
894+
],
895+
TransactionId: 'txn-1234',
896+
TotalAmount: 1451.98
897+
},
898+
CurrencyCode: 'USD',
899+
SourceMessageId: SOURCE_MESSAGE_ID,
900+
});
901+
902+
checkBasicProperties('track');
903+
window.fbqObj.should.have.property('eventName', 'Purchase');
904+
window.fbqObj.params.should.have.property('order_id', 'txn-1234');
905+
window.fbqObj.params.should.have.property('contents');
906+
window.fbqObj.params.contents.length.should.equal(2);
907+
908+
var firstProduct = window.fbqObj.params.contents[0];
909+
// Standard Facebook fields
910+
firstProduct.should.have.property('id', 'sku-12');
911+
firstProduct.should.have.property('name', 'iPhone');
912+
firstProduct.should.have.property('brand', 'Apple');
913+
firstProduct.should.have.property('item_price', 1000.99);
914+
firstProduct.should.have.property('quantity', 1);
915+
916+
// Mapped standard fields
917+
firstProduct.should.have.property('custom_name', 'iPhone');
918+
firstProduct.should.have.property('custom_brand', 'Apple');
919+
firstProduct.should.have.property('custom_price', 1000.99);
920+
firstProduct.should.have.property('custom_category', 'electronics');
921+
922+
// Mapped custom attribute
923+
firstProduct.should.have.property('custom_attribute_category', 'phones');
924+
925+
done();
926+
});
860927
});
861928
});

0 commit comments

Comments
 (0)