Skip to content

Commit

Permalink
Merge branch 'develop' into feature/sdk-1301-create-a-cookie-setter-p…
Browse files Browse the repository at this point in the history
…rovider-for-service
  • Loading branch information
MoumitaM authored May 21, 2024
2 parents 022d23b + e7e27be commit b0e1484
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,41 @@ describe('BingAds Track event', () => {
bingAds.init();
window.bing12567839.push = jest.fn((_, y, z) => output.push({ event: y, ...z }));
});

test('Test for custom properties with enhanced conversions using email only', () => {
bingAds = new BingAds(
{ tagID: '12567839', enableEnhancedConversions: true },
{ loglevel: 'DEBUG' },
);
bingAds.init();
window.bing12567839.push = jest.fn((_, y, z) => output.push({ event: y, ...z }));
bingAds.track({
message: {
type: 'track',
context: {
traits: { email: '[email protected]' },
},
event,
properties: {
event_action: 'button_click',
category: 'Food',
currency: 'INR',
customProp: 'custom',
},
},
});
expect(output[0]).toEqual({
event: 'button_click',
event_label: event,
event_category: 'Food',
currency: 'INR',
customProp: 'custom',
ecomm_pagetype: 'other',
pid: {
'': '',
em: 'ee278943de84e5d6243578ee1a1057bcce0e50daad9755f45dfa64b60b13bc5d',
},
});
});
test('Test for all properties not null', () => {
bingAds.track({
message: {
Expand All @@ -83,7 +117,7 @@ describe('BingAds Track event', () => {
},
},
});
expect(output[0]).toEqual({
expect(output[1]).toEqual({
event: 'button_click',
event_label: event,
event_category: 'Food',
Expand Down Expand Up @@ -117,7 +151,7 @@ describe('BingAds Track event', () => {
},
},
});
expect(output[1]).toEqual({
expect(output[2]).toEqual({
event: 'button_click',
event_label: event,
event_category: 'Food',
Expand All @@ -137,7 +171,7 @@ describe('BingAds Track event', () => {
},
});

expect(output[2]).toEqual({
expect(output[3]).toEqual({
event: 'track',
event_label: event,
ecomm_pagetype: 'other',
Expand All @@ -163,4 +197,62 @@ describe('BingAds Track event', () => {
expect(error).toEqual('Event type not present');
}
});
test('Test for all properties not null with pid data present in context.traits', () => {
bingAds = new BingAds(
{ tagID: '12567839', enableEnhancedConversions: true },
{ loglevel: 'DEBUG' },
);
bingAds.init();
window.bing12567839.push = jest.fn((_, y, z) => output.push({ event: y, ...z }));

bingAds.track({
message: {
type: 'track',
context: {
traits: {
pid: {
phn: '422ce82c6fc1724ac878042f7d055653ab5e983d186e616826a72d4384b68af8',
em: 'ee278943de84e5d6243578ee1a1057bcce0e50daad9755f45dfa64b60b13bc5d',
},
},
},
event,
properties: {
event_action: 'button_click',
category: 'Food',
currency: 'INR',
total: 18.9,
value: 20,
revenue: 25.5,
ecomm_category: '80',
transaction_id: 'txn-123',
ecomm_pagetype: 'Cart',
query,
products,
},
},
});
expect(output[4]).toEqual({
event: 'button_click',
event_label: event,
event_category: 'Food',
currency: 'INR',
revenue_value: 18.9,
search_term: query,
ecomm_query: query,
ecomm_category: '80',
transaction_id: 'txn-123',
ecomm_pagetype: 'Cart',
ecomm_prodid: ['123', '345'],
items: [
{ id: '123', price: 14.99, quantity: 2 },
{ id: '345', price: 3.99, quantity: 1 },
],
ecomm_totalvalue: 18.9,
pid: {
phn: '422ce82c6fc1724ac878042f7d055653ab5e983d186e616826a72d4384b68af8',
em: 'ee278943de84e5d6243578ee1a1057bcce0e50daad9755f45dfa64b60b13bc5d',
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
buildCommonPayload,
buildEcommPayload,
handleProductsArray,
constructPidPayload,
} from '../../../src/integrations/BingAds/utils';
import { query, products } from './__fixtures__/data';

Expand Down Expand Up @@ -136,3 +137,98 @@ describe('Build ecomm payload utility tests', () => {
});
});
});

describe('Construct PID payload for enahcned conversions', () => {
it('should return undefined when both email and phone are undefined', () => {
// Arrange
const message = {};

// Act
const result = constructPidPayload(message);

// Assert
expect(result).toEqual(undefined);
});

// Returns undefined when email is invalid
it('should return undefined when email is invalid and phone is not given', () => {
// Arrange
const message = {
context: {
traits: {
email: 'invalidemail',
},
},
};

// Act
const result = constructPidPayload(message);

// Assert
expect(result).toBeUndefined();
});

// Returns an object with only email property when only email is defined
it('should return an object with only email property when only email is defined', () => {
// Arrange
const message = {
context: {
traits: {
email: '[email protected]',
},
},
};

// Act
const result = constructPidPayload(message);

// Assert
expect(result).toEqual({
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b',
'': '',
});
});

// Returns an object with only phone property when only phone is defined
it('should return an object with only phone property when only phone is defined', () => {
// Arrange
const message = {
traits: {
phone: '1234567890',
},
};

// Act
const result = constructPidPayload(message);

// Assert
expect(result).toEqual({
ph: '422ce82c6fc1724ac878042f7d055653ab5e983d186e616826a72d4384b68af8',
'': '',
});
});

// Returns an object with both email and phone properties when both are defined
it('should return an object with both email and phone properties when both are defined', () => {
// Arrange
const message = {
context: {
traits: {
email: '[email protected]',
},
},
traits: {
phone: '1234567890',
},
};

// Act
const result = constructPidPayload(message);

// Assert
expect(result).toEqual({
em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b',
ph: '422ce82c6fc1724ac878042f7d055653ab5e983d186e616826a72d4384b68af8',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
import Logger from '../../utils/logger';
import { loadNativeSdk } from './nativeSdkLoader';
import { extractCustomFields } from '../../utils/utils';
import { buildCommonPayload, buildEcommPayload, EXCLUSION_KEYS } from './utils';
import {
buildCommonPayload,
buildEcommPayload,
EXCLUSION_KEYS,
constructPidPayload,
} from './utils';
import { removeUndefinedAndNullValues } from '../../utils/commonUtils';

const logger = new Logger(DISPLAY_NAME);
Expand All @@ -24,6 +29,7 @@ class BingAds {
destinationId: this.destinationId,
} = destinationInfo ?? {});
this.uniqueId = `bing${this.tagID}`;
this.enableEnhancedConversions = config.enableEnhancedConversions;
}

init() {
Expand All @@ -48,7 +54,7 @@ class BingAds {
*/

track = rudderElement => {
const { type, properties } = rudderElement.message;
const { type, properties, context } = rudderElement.message;
const eventToSend = properties?.event_action || type;
if (!eventToSend) {
logger.error('Event type is not present');
Expand All @@ -68,8 +74,10 @@ class BingAds {
);

payload = { ...payload, ...customProperties };
if (this.enableEnhancedConversions === true) {
payload.pid = context?.traits?.pid || constructPidPayload(context);
}
payload = removeUndefinedAndNullValues(payload);

window[this.uniqueId].push('event', eventToSend, payload);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import sha256 from 'crypto-js/sha256';
import get from 'get-value';
import { isDefinedAndNotNull } from '../../utils/commonUtils';

const EXCLUSION_KEYS = [
'event',
'category',
Expand Down Expand Up @@ -91,10 +95,104 @@ const buildEcommPayload = message => {
return { ...ecommPayload, ...payload };
};

/**
* Formats the email and hashes the email
* Docs for formatting email
* https://help.ads.microsoft.com/apex/index/3/en/60178#:~:text=do%20so%20manually.-,Format%20and%20hash%20the%20data,-Format%20the%20data
* @param {*} emailString
* @returns hash of finalised email and if it is invalid returns undefined
*/
const formatAndHashEmailAddress = emailString => {
// Remove whitespaces from the beginning and end of the email address and lower case it
let email = emailString.trim().toLowerCase();

// Remove everything between “+” and “@”
email = email.replace(/\+[^@]+/g, '');

// Remove any periods that come before “@”
email = email.replace(/\./g, (match, offset) => {
if (offset < email.indexOf('@')) {
return '';
}
return match;
});

/* Make sure
1. email address contains “@” sign
2. there is a period after “@”
3. it doesn't start or end with a period
*/
if (
!email.includes('@') ||
!/\.\w+$/.test(email) ||
email.startsWith('.') ||
email.endsWith('.')
) {
return undefined;
}

// Remove any spaces
email = email.replace(/\s/g, '');

// Remove any accents (ex: à)
email = email.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

// Calculate and return the SHA256 hash of the final email address
return sha256(email).toString();
};

/**
* Format and Hash PhoneNumber
* @param {*} phoneNumber
* @returnsformatted and hashed phone number
*/
function formatAndHashPhoneNumber(phoneNumber) {
// Remove any non-digit characters
const cleanedPhoneNumber = phoneNumber.replace(/\D/g, '');

// Check if the phone number starts with a '+' sign
const formattedPhoneNumber = cleanedPhoneNumber.startsWith('+')
? cleanedPhoneNumber
: `+${cleanedPhoneNumber}`;

// Calculate and return the SHA256 hash of the formatted phone number
return sha256(formattedPhoneNumber).toString();
}

/**
* Constructing Pid Payload
* @param {*} context
*/
const constructPidPayload = message => {
const email = get(message, 'context.traits.email') || get(message, 'traits.email');
const phone = get(message, 'context.traits.phone') || get(message, 'traits.phone');
const pid = {};
if (isDefinedAndNotNull(email)) {
pid.em = formatAndHashEmailAddress(email);
}
if (isDefinedAndNotNull(phone)) {
pid.ph = formatAndHashPhoneNumber(phone);
}
/* Docs: https://help.ads.microsoft.com/apex/index/3/en/60178
Bing ads says if anyone one of the properties is not available
and one is available then we need to send empty string as variable name for the one that is missing
*/
if (
(isDefinedAndNotNull(email) && !isDefinedAndNotNull(phone)) ||
(!isDefinedAndNotNull(email) && isDefinedAndNotNull(phone))
) {
pid[''] = '';
}

// If both email and phone are not present or are not in correct format we won't be able to do enhance conversion return undefined
if (!isDefinedAndNotNull(pid.em) && !isDefinedAndNotNull(pid.ph)) return undefined;
return pid;
};
export {
buildCommonPayload,
buildEcommPayload,
handleProductsArray,
EXCLUSION_KEYS,
DEFAULT_PAGETYPE,
};
constructPidPayload,
};

0 comments on commit b0e1484

Please sign in to comment.