diff --git a/src/app/analytics/meta.service.test.ts b/src/app/analytics/meta.service.test.ts index 0fee54777..07cd68d95 100644 --- a/src/app/analytics/meta.service.test.ts +++ b/src/app/analytics/meta.service.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; -import { trackLead, trackPurchase } from './meta.service'; +import { trackLead, trackPurchase, trackCheckoutStart } from './meta.service'; import localStorageService from 'services/local-storage.service'; describe('Meta Tracking Service', () => { @@ -158,7 +158,7 @@ describe('Meta Tracking Service', () => { expect(mockDataLayer[0].ecommerce.value).toBe(123.45); expect(typeof mockDataLayer[0].ecommerce.value).toBe('number'); - + expect(mockFbq).toHaveBeenCalledWith('track', 'Purchase', { value: 123.45, currency: 'EUR', @@ -166,4 +166,51 @@ describe('Meta Tracking Service', () => { }); }); }); -}); \ No newline at end of file + + describe('trackCheckoutStart', () => { + it('When valid data is provided, then checkout start event is pushed to dataLayer and fbq is called with InitiateCheckout', () => { + trackCheckoutStart({ value: 100, currency: 'EUR', content_ids: ['plan_1'] }); + + expect(mockDataLayer).toHaveLength(1); + expect(mockDataLayer[0]).toMatchObject({ + event: 'initiateCheckout', + eventCategory: 'User', + eventAction: 'checkout_start', + }); + + expect(mockFbq).toHaveBeenCalledWith('track', 'InitiateCheckout', { + content_type: 'product', + eventref: 'fb_oea', + value: 100, + currency: 'EUR', + content_ids: ['plan_1'], + }); + }); + + it('When no data is provided, it still fires InitiateCheckout with default payload', () => { + trackCheckoutStart(); + + expect(mockDataLayer).toHaveLength(1); + expect(mockFbq).toHaveBeenCalledWith('track', 'InitiateCheckout', { + content_type: 'product', + eventref: 'fb_oea', + }); + }); + + it('When dataLayer is not available, then no event is pushed', () => { + (globalThis.window as any).dataLayer = undefined; + + trackCheckoutStart(); + + expect(mockFbq).not.toHaveBeenCalled(); + }); + + it('When fbq is not available, then no event is pushed', () => { + (globalThis.window as any).fbq = undefined; + + trackCheckoutStart(); + + expect(mockDataLayer).toHaveLength(0); + }); + }); +}); diff --git a/src/app/analytics/meta.service.ts b/src/app/analytics/meta.service.ts index 0489e2a90..e0bf7ba7a 100644 --- a/src/app/analytics/meta.service.ts +++ b/src/app/analytics/meta.service.ts @@ -2,8 +2,7 @@ import localStorageService from 'services/local-storage.service'; const canTrack = () => { - return globalThis.window?.dataLayer && - globalThis.window?.fbq; + return globalThis.window?.dataLayer && globalThis.window?.fbq; }; export const trackLead = (email: string, userID: string) => { @@ -52,9 +51,37 @@ export const trackPurchase = () => { }); }; +export const trackCheckoutStart = (data?: { value?: number; currency?: string; content_ids?: string[] }) => { + if (!canTrack()) return; + + globalThis.window.dataLayer.push({ + event: 'initiateCheckout', + eventCategory: 'User', + eventAction: 'checkout_start', + }); + + const fbqPayload: any = { + content_type: 'product', + eventref: 'fb_oea', + }; + + if (data?.value !== undefined) { + fbqPayload.value = data.value; + } + if (data?.currency) { + fbqPayload.currency = data.currency; + } + if (data?.content_ids) { + fbqPayload.content_ids = data.content_ids; + } + + (globalThis.window as any).fbq('track', 'InitiateCheckout', fbqPayload); +}; + const metaService = { trackLead, trackPurchase, + trackCheckoutStart, }; -export default metaService; \ No newline at end of file +export default metaService; diff --git a/src/views/Checkout/views/CheckoutViewWrapper.tsx b/src/views/Checkout/views/CheckoutViewWrapper.tsx index c3e37d85c..2c5ff9af0 100644 --- a/src/views/Checkout/views/CheckoutViewWrapper.tsx +++ b/src/views/Checkout/views/CheckoutViewWrapper.tsx @@ -30,6 +30,7 @@ import { CRYPTO_PAYMENT_DIALOG_KEY, CryptoPaymentDialog } from 'views/Checkout/c import { useActionDialog } from 'app/contexts/dialog-manager/useActionDialog'; import { generateCaptchaToken } from 'utils/generateCaptchaToken'; import gaService from 'app/analytics/ga.service'; +import metaService from 'app/analytics/meta.service'; import { useCheckoutQueryParams } from '../hooks/useCheckoutQueryParams'; import { useInitializeCheckout } from '../hooks/useInitializeCheckout'; import { useProducts } from '../hooks/useProducts'; @@ -163,6 +164,12 @@ const CheckoutViewWrapper = () => { couponCodeData: promoCodeData, seats: selectedPlan.price.type === 'business' ? businessSeats : 1, }); + + metaService.trackCheckoutStart({ + value: selectedPlan.price.decimalAmount, + currency: selectedPlan.price.currency ?? 'eur', + content_ids: [selectedPlan.price.id], + }); } }, [isCheckoutReady]);