diff --git a/packages/analytics/src/__tests__/__snapshots__/index.test.ts.snap b/packages/analytics/src/__tests__/__snapshots__/index.test.ts.snap index f23a7b40b..511ca1880 100644 --- a/packages/analytics/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/analytics/src/__tests__/__snapshots__/index.test.ts.snap @@ -150,6 +150,7 @@ Object { "LOAD_INTEGRATION_TRACK_TYPE": "loadIntegration", "ON_SET_USER_TRACK_TYPE": "onSetUser", "StorageWrapper": [Function], + "getCheckoutOrderIdentificationProperties": [Function], "getCheckoutProperties": [Function], "getContextDefaults": [Function], "getCookie": [Function], diff --git a/packages/analytics/src/integrations/Omnitracking/__tests__/Omnitracking.test.ts b/packages/analytics/src/integrations/Omnitracking/__tests__/Omnitracking.test.ts index 261afea05..1dc8705c0 100644 --- a/packages/analytics/src/integrations/Omnitracking/__tests__/Omnitracking.test.ts +++ b/packages/analytics/src/integrations/Omnitracking/__tests__/Omnitracking.test.ts @@ -372,7 +372,7 @@ describe('Omnitracking', () => { await omnitracking.track(data); data = generateTrackMockData({ - event: EventType.PlaceOrderStarted, + event: EventType.Share, }); await omnitracking.track(data); diff --git a/packages/analytics/src/integrations/Omnitracking/__tests__/__snapshots__/Omnitracking.test.ts.snap b/packages/analytics/src/integrations/Omnitracking/__tests__/__snapshots__/Omnitracking.test.ts.snap index 621544be7..af6df86ae 100644 --- a/packages/analytics/src/integrations/Omnitracking/__tests__/__snapshots__/Omnitracking.test.ts.snap +++ b/packages/analytics/src/integrations/Omnitracking/__tests__/__snapshots__/Omnitracking.test.ts.snap @@ -118,8 +118,12 @@ Object { exports[`Omnitracking track events definitions \`Order Completed\` return should match the snapshot 1`] = ` Object { + "addressFinder": false, "checkoutOrderId": 21312312, + "checkoutStep": 5, + "deliveryInformationDetails": "{\\"deliveryType\\":\\"Standard/Standard\\",\\"courierType\\":\\"Next Day\\",\\"packagingType\\":\\"foo\\"}", "orderCode": "ABC12", + "paymentType": "credit card", "tid": 2831, } `; diff --git a/packages/analytics/src/integrations/Omnitracking/definitions.ts b/packages/analytics/src/integrations/Omnitracking/definitions.ts index 4d05dffe3..2b1a95d90 100644 --- a/packages/analytics/src/integrations/Omnitracking/definitions.ts +++ b/packages/analytics/src/integrations/Omnitracking/definitions.ts @@ -495,6 +495,10 @@ export const trackEventsMapper: Readonly = { [EventType.OrderCompleted]: data => ({ tid: 2831, ...getCheckoutEventGenericProperties(data), + addressFinder: data.properties?.addressFinder, + checkoutStep: data.properties?.step, + paymentType: data.properties?.paymentType, + deliveryInformationDetails: getDeliveryInformationDetails(data), }), [EventType.Logout]: () => ({ tid: 431, diff --git a/packages/analytics/src/integrations/Omnitracking/omnitracking-helper.ts b/packages/analytics/src/integrations/Omnitracking/omnitracking-helper.ts index e4978fcc5..f0147cf20 100644 --- a/packages/analytics/src/integrations/Omnitracking/omnitracking-helper.ts +++ b/packages/analytics/src/integrations/Omnitracking/omnitracking-helper.ts @@ -1,4 +1,9 @@ -import { ANALYTICS_UNIQUE_EVENT_ID } from '../../utils/constants.js'; +import { + ANALYTICS_UNIQUE_EVENT_ID, + getCheckoutOrderIdentificationProperties, + getProductId, + logger, +} from '../../utils/index.js'; import { type AnalyticsProduct, type EventData, @@ -12,7 +17,6 @@ import { DEFAULT_SEARCH_QUERY_PARAMETERS, } from './constants.js'; import { get, pick } from 'lodash-es'; -import { getProductId, logger } from '../../utils/index.js'; import { isPageEventType, isScreenEventType, @@ -522,19 +526,19 @@ export const getProductLineItems = (data: EventData) => { export const getCheckoutEventGenericProperties = ( data: EventData, ) => { - const validOrderCode = isNaN(Number(data.properties?.orderId)); + const orderInfo = getCheckoutOrderIdentificationProperties(data.properties); - if (!validOrderCode) { + if (orderInfo.orderCode === undefined) { logger.error( `[Omnitracking] - Event ${data.event} property "orderId" should be an alphanumeric value. - If you send the internal "orderId", please use "orderId" (e.g.: 5H5QYB) + If you want to send the internal "orderId", please use "orderId" (e.g.: 5H5QYB) and 'checkoutOrderId' (e.g.:123123123)`, ); } return { - orderCode: data.properties?.orderId, - checkoutOrderId: data.properties?.checkoutOrderId, + orderCode: orderInfo.orderCode, + checkoutOrderId: orderInfo.orderId, }; }; diff --git a/packages/analytics/src/utils/__tests__/getters.test.ts b/packages/analytics/src/utils/__tests__/getters.test.ts new file mode 100644 index 000000000..df088fcb3 --- /dev/null +++ b/packages/analytics/src/utils/__tests__/getters.test.ts @@ -0,0 +1,99 @@ +import * as getters from '../getters.js'; + +describe('getters', () => { + describe('getCheckoutOrderIdentificationProperties', () => { + it('Should retrieve order data with multiple test cases', () => { + const checkoutOrderId = 123; + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: null, + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: undefined, + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: 0, + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: '0', + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: 12345, + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: '12345', + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: 'A12345', + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: 'A12345', + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: NaN, + checkoutOrderId: checkoutOrderId, + }), + ).toEqual({ + orderCode: undefined, + orderId: checkoutOrderId, + }); + + expect( + getters.getCheckoutOrderIdentificationProperties({ + orderId: 123, + checkoutOrderId: undefined, + }), + ).toEqual({ + orderCode: undefined, + orderId: 123, + }); + }); + }); +}); diff --git a/packages/analytics/src/utils/getters.ts b/packages/analytics/src/utils/getters.ts index f561d882d..7d31ea456 100644 --- a/packages/analytics/src/utils/getters.ts +++ b/packages/analytics/src/utils/getters.ts @@ -126,3 +126,44 @@ export const getCookie = (name: string): string | void => { return parts.pop()?.split(';').shift(); } }; + +/** + * Obtain checkout order generic fields from analytics properties. + * + * @param dataProperties - The event's properties data. + * @param orderIdFieldName - The name of orderId field. Default as `checkoutOrderId`. + * @returns The checkout generic order data result. + */ +export const getCheckoutOrderIdentificationProperties = ( + eventProperties: EventProperties, +) => { + const checkoutOrderId = eventProperties?.checkoutOrderId; + const orderId = eventProperties?.orderId; + const orderIdAsNumber = Number(orderId); + // Valid order code should be an alphanumeric value. + // Should be used 'orderId' values like e.g.: 5H5QYB + // and for 'checkoutOrderId' values like e.g.:123123123 + const isOrderCodeValid = + typeof orderId === 'string' && isNaN(orderIdAsNumber); + const isOrderIdValid = + typeof checkoutOrderId === 'number' && !isNaN(checkoutOrderId); + + const orderCode = isOrderCodeValid ? orderId : undefined; + + let finalOrderId; + + // If we have a valid order id, i.e., the property checkoutOrderId from the + // event properties is a number, then we always use it. + if (isOrderIdValid) { + finalOrderId = checkoutOrderId; + } else if (!isOrderCodeValid) { + // If we do not have a valid order code and checkoutOrderId is not valid + // we use the orderId from the payload if it is not NaN + finalOrderId = !isNaN(orderIdAsNumber) ? orderIdAsNumber : undefined; + } + + return { + orderCode, + orderId: finalOrderId, + }; +}; diff --git a/packages/react/src/analytics/integrations/GA4/__tests__/__snapshots__/GA4.test.ts.snap b/packages/react/src/analytics/integrations/GA4/__tests__/__snapshots__/GA4.test.ts.snap index b34313338..f4e883d9c 100644 --- a/packages/react/src/analytics/integrations/GA4/__tests__/__snapshots__/GA4.test.ts.snap +++ b/packages/react/src/analytics/integrations/GA4/__tests__/__snapshots__/GA4.test.ts.snap @@ -472,10 +472,13 @@ Array [ "purchase", Object { "__blackoutAnalyticsEventId": "4eabf689-96e3-4952-8176-248a848f1e1f", + "address_finder": false, "affiliation": undefined, "analytics_package_version": "0.1.0", + "checkout_step": 5, "coupon": "ACME2019", "currency": "USD", + "delivery_type": "Standard/Standard", "items": Array [ Object { "affiliation": undefined, @@ -498,9 +501,12 @@ Array [ "size": "L", }, ], + "packaging_type": "foo", "page_path": "/en-pt/?utm_term=utm_term&utm_source=utm_source&utm_medium=utm_medium&utm_content=utm_content&utm_campaign=utm_campaign", "path_clean": "/en-pt/", + "payment_type": "credit card", "shipping": 3.6, + "shipping_tier": "Next Day", "tax": 2.04, "transaction_id": "ABC12", "value": 24.64, diff --git a/packages/react/src/analytics/integrations/GA4/eventMapping.ts b/packages/react/src/analytics/integrations/GA4/eventMapping.ts index 24033de46..f2102d496 100644 --- a/packages/react/src/analytics/integrations/GA4/eventMapping.ts +++ b/packages/react/src/analytics/integrations/GA4/eventMapping.ts @@ -572,7 +572,7 @@ const getLoginAndSignupParametersFromEvent = ( * * @returns Properties formatted for the GA4's order completed/refunded ecommerce events. */ -const getOrderPurchaseOrRefundParametersFromEvent = ( +const getOrderPurchaseOrRefundGenericParametersFromEvent = ( eventProperties: EventProperties, ) => { return { @@ -584,6 +584,33 @@ const getOrderPurchaseOrRefundParametersFromEvent = ( }; }; +/** + * Returns the checkout order completed event properties formatted for the GA4 ecommerce events with some custom parameters. + * + * @see {@link https://developers.google.com/analytics/devguides/collection/ga4/ecommerce#purchases_checkouts_and_refunds} + * + * @param eventProperties - Properties from a track event. + * + * @returns Properties formatted for the GA4's order completed/refunded ecommerce events. + */ +const getOrderPurchaseParametersFromEvent = ( + eventProperties: EventProperties, +) => { + const orderInfo = + utils.getCheckoutOrderIdentificationProperties(eventProperties); + + return { + ...getOrderPurchaseOrRefundGenericParametersFromEvent(eventProperties), + address_finder: eventProperties.addressFinder, + checkout_step: eventProperties.step, + delivery_type: eventProperties.deliveryType, + payment_type: eventProperties.paymentType, + packaging_type: eventProperties.packagingType, + shipping_tier: eventProperties.shippingTier, + transaction_id: orderInfo.orderCode, + }; +}; + /** * Returns the place order started custom event properties formatted for the GA4 * ecommerce events. As it returns the same properties of a purchase event, it uses @@ -844,8 +871,11 @@ export function getEventProperties( return getViewItemParametersFromEvent(eventProperties); case EventType.OrderCompleted: + return getOrderPurchaseParametersFromEvent(eventProperties); case EventType.OrderRefunded: - return getOrderPurchaseOrRefundParametersFromEvent(eventProperties); + return getOrderPurchaseOrRefundGenericParametersFromEvent( + eventProperties, + ); case PageType.Search: return getSearchParametersFromEvent(eventProperties); diff --git a/tests/__fixtures__/analytics/track/orderCompletedTrackData.fixtures.mts b/tests/__fixtures__/analytics/track/orderCompletedTrackData.fixtures.mts index 98d57ac7a..8ac9d9d09 100644 --- a/tests/__fixtures__/analytics/track/orderCompletedTrackData.fixtures.mts +++ b/tests/__fixtures__/analytics/track/orderCompletedTrackData.fixtures.mts @@ -12,6 +12,12 @@ const fixtures = { tax: 2.04, coupon: 'ACME2019', currency: 'USD', + addressFinder: false, + step: 5, + deliveryType: 'Standard/Standard', + packagingType: 'foo', + shippingTier: 'Next Day', + paymentType: 'credit card', products: [ { id: '507f1f77bcf86cd799439011',