diff --git a/src/app/analytics/impact.service.test.ts b/src/app/analytics/impact.service.test.ts index cc1f0c371..991cb760d 100644 --- a/src/app/analytics/impact.service.test.ts +++ b/src/app/analytics/impact.service.test.ts @@ -9,6 +9,7 @@ import { getProductAmount } from 'views/Checkout/utils'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { handleImpactDTCCheckout, + resolvePartnerIdFromUrl, savePaymentDataInLocalStorage, trackPaymentConversion, trackSignUp, @@ -490,7 +491,7 @@ describe('handleImpactDTCCheckout', () => { it('When an affiliate partner is identified, then it includes their information in the tracking event', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); - await handleImpactDTCCheckout({ irclickid, utmMedium }); + await handleImpactDTCCheckout({ irclickid, partnerId: utmMedium }); const callArgs = axiosSpy.mock.calls[0][1] as { properties: Record }; expect(callArgs.properties).toHaveProperty('partner_id', utmMedium); @@ -514,7 +515,7 @@ describe('handleImpactDTCCheckout', () => { const setImpactCookiesSpy = vi.mocked(getCookieMock.setImpactCookies); vi.spyOn(axios, 'post').mockResolvedValue({}); - await handleImpactDTCCheckout({ irclickid, utmMedium }); + await handleImpactDTCCheckout({ irclickid, partnerId: utmMedium }); expect(setImpactCookiesSpy).toHaveBeenCalledWith('anon_id', irclickid, utmMedium); }); @@ -530,6 +531,32 @@ describe('handleImpactDTCCheckout', () => { }); }); +describe('resolvePartnerIdFromUrl', () => { + it('When utm_content is present, then it returns it as the partner ID', () => { + expect(resolvePartnerIdFromUrl('?utm_content=partner_abc')).toBe('partner_abc'); + }); + + it('When utm_medium is a numeric string, then it returns it as the partner ID', () => { + expect(resolvePartnerIdFromUrl('?utm_medium=312695')).toBe('312695'); + }); + + it('When both utm_content and utm_medium are present, then utm_content takes priority', () => { + expect(resolvePartnerIdFromUrl('?utm_content=partner_abc&utm_medium=312695')).toBe('partner_abc'); + }); + + it('When utm_medium is non-numeric, then it returns null', () => { + expect(resolvePartnerIdFromUrl('?utm_medium=google')).toBeNull(); + }); + + it('When neither utm_content nor utm_medium are present, then it returns null', () => { + expect(resolvePartnerIdFromUrl('?utm_source=newsletter')).toBeNull(); + }); + + it('When the search string is empty, then it returns null', () => { + expect(resolvePartnerIdFromUrl('')).toBeNull(); + }); +}); + describe('uuid library', () => { it('When calling v4, then it generates a valid UUID', async () => { const { v4 } = await vi.importActual('uuid'); @@ -815,7 +842,7 @@ describe('handleImpactDTCCheckout - additional coverage', () => { it('When no affiliate partner is identified, then no partner information is included in the event', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); - await handleImpactDTCCheckout({ irclickid, utmMedium: null }); + await handleImpactDTCCheckout({ irclickid, partnerId: null }); const callArgs = axiosSpy.mock.calls[0][1] as { properties: Record }; expect(callArgs.properties).not.toHaveProperty('partner_id'); diff --git a/src/app/analytics/impact.service.ts b/src/app/analytics/impact.service.ts index 55a2d0801..7b557b208 100644 --- a/src/app/analytics/impact.service.ts +++ b/src/app/analytics/impact.service.ts @@ -70,19 +70,35 @@ export function savePaymentDataInLocalStorage({ localStorageService.set('isFirstPurchase', String(isFirstPurchase)); } +export function resolvePartnerIdFromUrl(search: string = globalThis.location.search): string | null { + const params = new URLSearchParams(search); + const utmContent = params.get('utm_content'); + const utmMedium = params.get('utm_medium'); + + if (utmContent) { + return utmContent; + } + + if (utmMedium && /^\d+$/.test(utmMedium)) { + return utmMedium; + } + + return null; +} + export async function handleImpactDTCCheckout({ irclickid, - utmMedium, + partnerId, }: { irclickid: string; - utmMedium?: string | null; + partnerId?: string | null; }): Promise { try { const IMPACT_API = envService.getVariable('impactApiUrl'); const existingAnonymousId = getCookie('impactAnonymousId'); const anonymousId = existingAnonymousId || uuidV4(); - setImpactCookies(anonymousId, irclickid, utmMedium); + setImpactCookies(anonymousId, irclickid, partnerId); await axios.post(IMPACT_API, { anonymousId, @@ -97,7 +113,7 @@ export async function handleImpactDTCCheckout({ type: 'page', properties: { irclickid, - ...(utmMedium && { partner_id: utmMedium }), + ...(partnerId && { partner_id: partnerId }), }, }); } catch (error) { diff --git a/src/views/Checkout/views/CheckoutViewWrapper.tsx b/src/views/Checkout/views/CheckoutViewWrapper.tsx index 8a6b1cc80..b6134130e 100644 --- a/src/views/Checkout/views/CheckoutViewWrapper.tsx +++ b/src/views/Checkout/views/CheckoutViewWrapper.tsx @@ -122,7 +122,7 @@ const CheckoutViewWrapper = () => { localStorageService.set(STORAGE_KEYS.GCLID, gclid); } if (irclickid) { - handleImpactDTCCheckout({ irclickid, utmMedium }); + handleImpactDTCCheckout({ irclickid, partnerId: utmMedium }); } referralService.captureUcc(); }, []);