From 94d2df0930dee85cc5aa37405604ab19902fea2a Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Fri, 24 Apr 2026 11:37:47 +0200 Subject: [PATCH 1/4] remove subscriptionId from checkout tracking and unify under paymentIntentId --- src/app/analytics/ga.service.test.ts | 32 +---------- src/app/analytics/ga.service.ts | 5 +- src/app/analytics/impact.service.test.ts | 55 +++++++++---------- src/app/analytics/impact.service.ts | 10 +--- src/views/Checkout/hooks/useUserPayment.ts | 2 - .../Checkout/views/CheckoutSuccessView.tsx | 1 - 6 files changed, 31 insertions(+), 74 deletions(-) diff --git a/src/app/analytics/ga.service.test.ts b/src/app/analytics/ga.service.test.ts index 6968213d1a..eafd75433e 100644 --- a/src/app/analytics/ga.service.test.ts +++ b/src/app/analytics/ga.service.test.ts @@ -211,7 +211,6 @@ describe('Testing GA Service', () => { vi.mocked(localStorageService.get).mockImplementation((key) => { const store: Record = { - subscriptionId: 'sub_12345', paymentIntentId: '', priceId: 'price_yearly_2tb', currency: 'EUR', @@ -236,7 +235,7 @@ describe('Testing GA Service', () => { expect(event).toMatchObject({ event: 'purchase', ecommerce: { - transaction_id: 'sub_12345', + transaction_id: 'user_uuid_123', currency: 'EUR', value: 95.9, items: [ @@ -260,7 +259,6 @@ describe('Testing GA Service', () => { vi.mocked(localStorageService.getUser).mockReturnValue({ uuid: 'user_uuid' } as any); vi.mocked(localStorageService.get).mockImplementation((key) => { if (key === 'paymentIntentId') return 'pi_999'; - if (key === 'subscriptionId') return 'sub_888'; if (key === 'amountPaid') return '100'; if (key === 'itemOriginalPrice') return '119.88'; if (key === 'checkout_item_data') @@ -279,34 +277,10 @@ describe('Testing GA Service', () => { expect(event.ecommerce.transaction_id).toBe('pi_999'); }); - it('should use subscription ID when payment intent is not available', () => { - vi.mocked(localStorageService.getUser).mockReturnValue({ uuid: 'user_uuid' } as any); - vi.mocked(localStorageService.get).mockImplementation((key) => { - if (key === 'paymentIntentId') return null; - if (key === 'subscriptionId') return 'sub_888'; - if (key === 'amountPaid') return '100'; - if (key === 'itemOriginalPrice') return '119.88'; - if (key === 'checkout_item_data') - return JSON.stringify({ - item_name: '2TB Year Plan', - item_category: 'Individual', - item_variant: 'year', - discount: 0, - }); - return ''; - }); - - gaService.trackPurchase(); - - const event = globalThis.window.dataLayer[0] as any; - expect(event.ecommerce.transaction_id).toBe('sub_888'); - }); - - it('should fallback to user UUID when neither payment intent nor subscription ID are available', () => { + it('should fallback to user UUID when payment intent is not available', () => { vi.mocked(localStorageService.getUser).mockReturnValue({ uuid: 'user_fallback_uuid' } as any); vi.mocked(localStorageService.get).mockImplementation((key) => { if (key === 'paymentIntentId') return null; - if (key === 'subscriptionId') return null; if (key === 'amountPaid') return '100'; if (key === 'itemOriginalPrice') return '119.88'; if (key === 'checkout_item_data') @@ -513,4 +487,4 @@ describe('Testing GA Service', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/app/analytics/ga.service.ts b/src/app/analytics/ga.service.ts index 859af6679e..5eb81123d5 100644 --- a/src/app/analytics/ga.service.ts +++ b/src/app/analytics/ga.service.ts @@ -138,7 +138,7 @@ function trackBeginCheckout(params: TrackBeginCheckoutParams): void { } } - function trackPurchase(): void { +function trackPurchase(): void { try { const userSettings = localStorageService.getUser() as UserSettings; if (!userSettings) { @@ -154,7 +154,6 @@ function trackBeginCheckout(params: TrackBeginCheckoutParams): void { return; } - const subscriptionId = localStorageService.get('subscriptionId'); const paymentIntentId = localStorageService.get('paymentIntentId'); const priceId = localStorageService.get('priceId'); const currency = localStorageService.get('currency'); @@ -173,7 +172,7 @@ function trackBeginCheckout(params: TrackBeginCheckoutParams): void { console.error('[GA Service] Error parsing checkout_item_data:', parseError); } - const transactionId = paymentIntentId || subscriptionId || uuid; + const transactionId = paymentIntentId || uuid; const currencyCode = currency ?? 'EUR'; const itemName = checkoutItemData?.item_name || 'Unknown Plan'; diff --git a/src/app/analytics/impact.service.test.ts b/src/app/analytics/impact.service.test.ts index d7b7cd352f..8b35af0a04 100644 --- a/src/app/analytics/impact.service.test.ts +++ b/src/app/analytics/impact.service.test.ts @@ -42,7 +42,6 @@ vi.mock('services/error.service', () => ({ }, })); -const subId = 'sub_123'; const paymentIntentId = 'py_123'; const mockedUserUuid = '00000000-0000-0000-0000-0000000000'; const mockImpactApiUrl = 'mock-impact-api-url'; @@ -93,7 +92,6 @@ beforeEach(() => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'paymentIntentId') return paymentIntentId; - if (key === 'subscriptionId') return subId; if (key === 'productName') return planName; if (key === 'priceId') return product.price.id; if (key === 'currency') return product.price.currency; @@ -110,7 +108,6 @@ describe('Testing Impact Service', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ - subscriptionId: subId, paymentIntentId, selectedPlan: product as PriceWithTax, users: 1, @@ -121,32 +118,12 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('amountPaid', expectedAmount); }); - it('should save subscription ID when plan is not lifetime', () => { + it('should save payment intent ID if provided', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ - subscriptionId: subId, - paymentIntentId: undefined, - selectedPlan: product as PriceWithTax, - users: 1, - couponCodeData: promoCode, - isFirstPurchase: true, - }); - - expect(setToLocalStorageSpy).toHaveBeenCalledWith('subscriptionId', subId); - }); - - it('should save payment intent ID when plan is lifetime', () => { - const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); - const lifetimeProduct = { - ...product, - price: { ...product.price, interval: 'lifetime' }, - }; - - savePaymentDataInLocalStorage({ - subscriptionId: undefined, paymentIntentId, - selectedPlan: lifetimeProduct as PriceWithTax, + selectedPlan: product as PriceWithTax, users: 1, couponCodeData: promoCode, isFirstPurchase: true, @@ -159,7 +136,6 @@ describe('Testing Impact Service', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ - subscriptionId: subId, paymentIntentId, selectedPlan: product as PriceWithTax, users: 1, @@ -176,7 +152,6 @@ describe('Testing Impact Service', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ - subscriptionId: subId, paymentIntentId, selectedPlan: product as PriceWithTax, users: 1, @@ -191,7 +166,6 @@ describe('Testing Impact Service', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ - subscriptionId: subId, paymentIntentId, selectedPlan: product as PriceWithTax, users: 1, @@ -287,7 +261,6 @@ describe('Testing Impact Service', () => { timestamp: expect.any(String), properties: expect.objectContaining({ impact_value: parseFloat(expectedAmount), - subscription_id: subId, payment_intent: paymentIntentId, order_promo_code: promoCode.codeName, }), @@ -301,7 +274,6 @@ describe('Testing Impact Service', () => { it('should use minimum value of 0.01 when amount is 0 (free purchase)', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'amountPaid') return '0'; - if (key === 'subscriptionId') return subId; if (key === 'couponCode') return promoCode.codeName; if (key === 'isFirstPurchase') return 'true'; return null; @@ -353,6 +325,29 @@ describe('Testing Impact Service', () => { expect(axiosSpy).not.toHaveBeenCalled(); }); + it('should send to Impact when source is direct but coupon code is present', async () => { + const getCookieMock = await import('./utils'); + vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { + if (key === 'impactSource') return 'direct'; + if (key === 'impactAnonymousId') return ''; + return ''; + }); + vi.spyOn(localStorageService, 'get').mockImplementation((key) => { + if (key === 'couponCode') return 'CNINTERNXT'; + if (key === 'amountPaid') return expectedAmount; + if (key === 'isFirstPurchase') return 'true'; + return null; + }); + const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); + + await trackPaymentConversion(); + + expect(axiosSpy).toHaveBeenCalledTimes(1); + const callArgs = axiosSpy.mock.calls[0][1] as { properties: Record; anonymousId: string }; + expect(callArgs.properties).toHaveProperty('order_promo_code', 'CNINTERNXT'); + expect(callArgs.anonymousId).toBe(''); + }); + it('should not send to Impact when isFirstPurchase is false', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'isFirstPurchase') return 'false'; diff --git a/src/app/analytics/impact.service.ts b/src/app/analytics/impact.service.ts index a0ff1268a5..33d19c791d 100644 --- a/src/app/analytics/impact.service.ts +++ b/src/app/analytics/impact.service.ts @@ -29,7 +29,6 @@ import { sendAddShoppersConversion } from './addShoppers.services'; * */ export interface SavePaymentDataParams { - subscriptionId: string | undefined; paymentIntentId: string | undefined; selectedPlan: PriceWithTax | undefined; users: number; @@ -38,18 +37,13 @@ export interface SavePaymentDataParams { } export function savePaymentDataInLocalStorage({ - subscriptionId, paymentIntentId, selectedPlan, users, couponCodeData, isFirstPurchase, }: SavePaymentDataParams) { - if (subscriptionId && selectedPlan?.price.interval !== 'lifetime') { - localStorageService.set('subscriptionId', subscriptionId); - } - - if (paymentIntentId && selectedPlan?.price.interval === 'lifetime') { + if (paymentIntentId) { localStorageService.set('paymentIntentId', paymentIntentId); } @@ -107,7 +101,6 @@ export async function trackPaymentConversion(): Promise { } const { uuid, email: userEmail } = userSettings; - const subscription = localStorageService.get('subscriptionId'); const paymentIntent = localStorageService.get('paymentIntentId'); const currency = localStorageService.get('currency'); const amountPaidStr = localStorageService.get('amountPaid'); @@ -138,7 +131,6 @@ export async function trackPaymentConversion(): Promise { timestamp: dayjs().format('YYYY-MM-DDTHH:mm:ss.sssZ'), properties: { impact_value: amount === 0 ? 0.01 : amount, - subscription_id: subscription, payment_intent: paymentIntent, ...(couponCode && { order_promo_code: couponCode }), }, diff --git a/src/views/Checkout/hooks/useUserPayment.ts b/src/views/Checkout/hooks/useUserPayment.ts index 8fbf822748..f2afdc6b8c 100644 --- a/src/views/Checkout/hooks/useUserPayment.ts +++ b/src/views/Checkout/hooks/useUserPayment.ts @@ -167,7 +167,6 @@ export const useUserPayment = () => { }); savePaymentDataInLocalStorage({ - subscriptionId: subscription.subscriptionId, paymentIntentId: subscription.paymentIntentId, selectedPlan: currentSelectedPlan, users: seatsForBusinessSubscription, @@ -225,7 +224,6 @@ export const useUserPayment = () => { }); savePaymentDataInLocalStorage({ - subscriptionId: undefined, paymentIntentId, selectedPlan: currentSelectedPlan, users: 1, diff --git a/src/views/Checkout/views/CheckoutSuccessView.tsx b/src/views/Checkout/views/CheckoutSuccessView.tsx index 7db0d70afd..e3f26ddc01 100644 --- a/src/views/Checkout/views/CheckoutSuccessView.tsx +++ b/src/views/Checkout/views/CheckoutSuccessView.tsx @@ -9,7 +9,6 @@ import gaService from 'app/analytics/ga.service'; import metaService from 'app/analytics/meta.service'; export function removePaymentsStorage() { - localStorageService.removeItem('subscriptionId'); localStorageService.removeItem('paymentIntentId'); localStorageService.removeItem('amountPaid'); localStorageService.removeItem('productName'); From c05a0f8f0c49f75b41aa48353352233474f58219 Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Mon, 11 May 2026 11:03:17 +0200 Subject: [PATCH 2/4] Update impact.service.test.ts --- src/app/analytics/impact.service.test.ts | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/app/analytics/impact.service.test.ts b/src/app/analytics/impact.service.test.ts index ce533e86ac..e7e76b4628 100644 --- a/src/app/analytics/impact.service.test.ts +++ b/src/app/analytics/impact.service.test.ts @@ -104,7 +104,7 @@ beforeEach(() => { describe('Testing Impact Service', () => { describe('savePaymentDataInLocalStorage', () => { - it('should save the correct amount to localStorage after applying coupon', () => { + it('When coupon is applied, then it saves the correct amount to localStorage', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -118,7 +118,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('amountPaid', expectedAmount); }); - it('should save payment intent ID if provided', () => { + it('When payment intent ID is provided, then it saves payment intent ID', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -132,7 +132,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('paymentIntentId', paymentIntentId); }); - it('should save product metadata including name, price ID, and currency', () => { + it('When saving payment data, then it saves product metadata including name, price ID, and currency', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -148,7 +148,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('currency', product.price.currency); }); - it('should save coupon code when provided', () => { + it('When coupon code is provided, then it saves coupon code', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -162,7 +162,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('couponCode', promoCode.codeName); }); - it('should save isFirstPurchase flag to localStorage', () => { + it('When saving payment data, then it saves isFirstPurchase flag to localStorage', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -179,7 +179,7 @@ describe('Testing Impact Service', () => { describe('trackSignUp', () => { describe('gtag tracking', () => { - it('should send User Signup event to gtag', async () => { + it('When trackSignUp is called, then it sends User Signup event to gtag', async () => { const gTagSpy = vi.spyOn(globalThis.window, 'gtag'); await trackSignUp(mockedUserUuid); @@ -187,7 +187,7 @@ describe('Testing Impact Service', () => { expect(gTagSpy).toHaveBeenCalledWith('event', 'User Signup'); }); - it('should report error when gtag fails but continue execution', async () => { + it('When gtag fails, then it reports error but continues execution', async () => { const unknownError = new Error('gtag Error'); const gTagSpy = vi.spyOn(globalThis.window, 'gtag').mockImplementation(() => { throw unknownError; @@ -202,7 +202,7 @@ describe('Testing Impact Service', () => { }); describe('Impact API tracking', () => { - it('should send signup event to Impact API with correct payload', async () => { + it('When trackSignUp is called, then it sends signup event to Impact API with correct payload', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackSignUp(mockedUserUuid); @@ -220,7 +220,7 @@ describe('Testing Impact Service', () => { ); }); - it('should include message ID in Impact API payload', async () => { + it('When trackSignUp is called, then it includes message ID in Impact API payload', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackSignUp(mockedUserUuid); @@ -230,7 +230,7 @@ describe('Testing Impact Service', () => { expect(callArgs.messageId).toBe(mockedUserUuid); }); - it('should not send to Impact API when source is direct', async () => { + it('When source is direct, then it does not send to Impact API', async () => { const getCookieMock = await import('./utils'); vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { if (key === 'impactSource') return 'direct'; @@ -248,7 +248,7 @@ describe('Testing Impact Service', () => { describe('trackPaymentConversion', () => { describe('Impact API tracking', () => { - it('should send payment conversion to Impact API with correct data', async () => { + it('When trackPaymentConversion is called, then it sends payment conversion to Impact API with correct data', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackPaymentConversion(); @@ -271,7 +271,7 @@ describe('Testing Impact Service', () => { ); }); - it('should use minimum value of 0.01 when amount is 0 (free purchase)', async () => { + it('When amount is 0 (free purchase), then it uses minimum value of 0.01', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'amountPaid') return '0'; if (key === 'couponCode') return promoCode.codeName; @@ -286,7 +286,7 @@ describe('Testing Impact Service', () => { expect(callArgs.properties.impact_value).toBe(0.01); }); - it('should include coupon code in properties when available', async () => { + it('When coupon code is available, then it includes coupon code in properties', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackPaymentConversion(); @@ -295,7 +295,7 @@ describe('Testing Impact Service', () => { expect(callArgs.properties).toHaveProperty('order_promo_code', promoCode.codeName); }); - it('should report error when Impact API call fails', async () => { + it('When Impact API call fails, then it reports error', async () => { const unknownError = new Error('API Error'); const axiosSpy = vi.spyOn(axios, 'post').mockRejectedValue(unknownError); const errorServiceSpy = vi.spyOn(errorService, 'reportError'); @@ -306,7 +306,7 @@ describe('Testing Impact Service', () => { expect(errorServiceSpy).toHaveBeenCalledWith(unknownError); }); - it('should not send to Impact when source is direct and no coupon code', async () => { + it('When source is direct and no coupon code is present, then it does not send to Impact', async () => { const getCookieMock = await import('./utils'); vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { if (key === 'impactSource') return 'direct'; @@ -325,7 +325,7 @@ describe('Testing Impact Service', () => { expect(axiosSpy).not.toHaveBeenCalled(); }); - it('should send to Impact when source is direct but coupon code is present', async () => { + it('When source is direct but coupon code is present, then it sends to Impact', async () => { const getCookieMock = await import('./utils'); vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { if (key === 'impactSource') return 'direct'; @@ -348,7 +348,7 @@ describe('Testing Impact Service', () => { expect(callArgs.anonymousId).toBe(''); }); - it('should not send to Impact when isFirstPurchase is false', async () => { + it('When isFirstPurchase is false, then it does not send to Impact', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'isFirstPurchase') return 'false'; if (key === 'amountPaid') return expectedAmount; @@ -364,7 +364,7 @@ describe('Testing Impact Service', () => { }); describe('Error handling', () => { - it('should handle missing user settings gracefully', async () => { + it('When user settings are missing, then it handles them gracefully', async () => { const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.spyOn(localStorageService, 'getUser').mockReturnValue(null); @@ -373,13 +373,13 @@ describe('Testing Impact Service', () => { consoleWarnSpy.mockRestore(); }); - it('should continue execution when gtag is not available', async () => { + it('When gtag is not available, then it continues execution', async () => { globalThis.window.gtag = undefined as any; await expect(trackPaymentConversion()).resolves.not.toThrow(); }); - it('should handle errors in entire function gracefully', async () => { + it('When an error occurs in the entire function, then it handles it gracefully', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.spyOn(localStorageService, 'getUser').mockImplementation(() => { throw new Error('Storage Error'); @@ -394,7 +394,7 @@ describe('Testing Impact Service', () => { }); describe('uuid library', () => { - it('v4 generates a valid UUID', async () => { + it('When calling v4, then it generates a valid UUID', async () => { const { v4 } = await vi.importActual('uuid'); const id = v4(); expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); From 2ae3e48661efd3aff3c081325a27b190cf9654e1 Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Mon, 11 May 2026 12:32:57 +0200 Subject: [PATCH 3/4] Update impact.service.test.ts --- src/app/analytics/impact.service.test.ts | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/analytics/impact.service.test.ts b/src/app/analytics/impact.service.test.ts index e7e76b4628..5fbfd8da69 100644 --- a/src/app/analytics/impact.service.test.ts +++ b/src/app/analytics/impact.service.test.ts @@ -104,7 +104,7 @@ beforeEach(() => { describe('Testing Impact Service', () => { describe('savePaymentDataInLocalStorage', () => { - it('When coupon is applied, then it saves the correct amount to localStorage', () => { + it('When a coupon is applied, then the discounted amount is stored for conversion tracking', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -118,7 +118,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('amountPaid', expectedAmount); }); - it('When payment intent ID is provided, then it saves payment intent ID', () => { + it('When a payment intent is provided, then the payment reference is stored for tracking', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -132,7 +132,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('paymentIntentId', paymentIntentId); }); - it('When saving payment data, then it saves product metadata including name, price ID, and currency', () => { + it('When payment data is saved, then the plan name, price, and currency are all stored', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -148,7 +148,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('currency', product.price.currency); }); - it('When coupon code is provided, then it saves coupon code', () => { + it('When a coupon code is used, then it is stored alongside the payment data', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -162,7 +162,7 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('couponCode', promoCode.codeName); }); - it('When saving payment data, then it saves isFirstPurchase flag to localStorage', () => { + it('When payment data is saved, then whether it is the first purchase is also stored', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); savePaymentDataInLocalStorage({ @@ -179,7 +179,7 @@ describe('Testing Impact Service', () => { describe('trackSignUp', () => { describe('gtag tracking', () => { - it('When trackSignUp is called, then it sends User Signup event to gtag', async () => { + it('When a user signs up, then the registration is reported to Google Analytics', async () => { const gTagSpy = vi.spyOn(globalThis.window, 'gtag'); await trackSignUp(mockedUserUuid); @@ -187,7 +187,7 @@ describe('Testing Impact Service', () => { expect(gTagSpy).toHaveBeenCalledWith('event', 'User Signup'); }); - it('When gtag fails, then it reports error but continues execution', async () => { + it('When Google Analytics fails, then the error is logged and sign-up tracking continues', async () => { const unknownError = new Error('gtag Error'); const gTagSpy = vi.spyOn(globalThis.window, 'gtag').mockImplementation(() => { throw unknownError; @@ -248,7 +248,7 @@ describe('Testing Impact Service', () => { describe('trackPaymentConversion', () => { describe('Impact API tracking', () => { - it('When trackPaymentConversion is called, then it sends payment conversion to Impact API with correct data', async () => { + it('When a payment is completed, then the full order details are reported to Impact', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackPaymentConversion(); @@ -271,7 +271,7 @@ describe('Testing Impact Service', () => { ); }); - it('When amount is 0 (free purchase), then it uses minimum value of 0.01', async () => { + it('When the purchase is free, then the minimum trackable amount is used in the conversion report', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'amountPaid') return '0'; if (key === 'couponCode') return promoCode.codeName; @@ -286,7 +286,7 @@ describe('Testing Impact Service', () => { expect(callArgs.properties.impact_value).toBe(0.01); }); - it('When coupon code is available, then it includes coupon code in properties', async () => { + it('When a promo code was used, then it appears in the conversion data reported to Impact', async () => { const axiosSpy = vi.spyOn(axios, 'post').mockResolvedValue({}); await trackPaymentConversion(); @@ -295,7 +295,7 @@ describe('Testing Impact Service', () => { expect(callArgs.properties).toHaveProperty('order_promo_code', promoCode.codeName); }); - it('When Impact API call fails, then it reports error', async () => { + it('When reporting to Impact fails, then the error is logged', async () => { const unknownError = new Error('API Error'); const axiosSpy = vi.spyOn(axios, 'post').mockRejectedValue(unknownError); const errorServiceSpy = vi.spyOn(errorService, 'reportError'); @@ -306,7 +306,7 @@ describe('Testing Impact Service', () => { expect(errorServiceSpy).toHaveBeenCalledWith(unknownError); }); - it('When source is direct and no coupon code is present, then it does not send to Impact', async () => { + it('When the traffic source is direct and no promo code was used, then the conversion is not sent to Impact', async () => { const getCookieMock = await import('./utils'); vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { if (key === 'impactSource') return 'direct'; @@ -325,7 +325,7 @@ describe('Testing Impact Service', () => { expect(axiosSpy).not.toHaveBeenCalled(); }); - it('When source is direct but coupon code is present, then it sends to Impact', async () => { + it('When the traffic source is direct but a promo code was used, then the conversion is still sent to Impact', async () => { const getCookieMock = await import('./utils'); vi.mocked(getCookieMock.getCookie).mockImplementation((key) => { if (key === 'impactSource') return 'direct'; @@ -348,7 +348,7 @@ describe('Testing Impact Service', () => { expect(callArgs.anonymousId).toBe(''); }); - it('When isFirstPurchase is false, then it does not send to Impact', async () => { + it('When the purchase is not the first, then it is not reported as a conversion to Impact', async () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'isFirstPurchase') return 'false'; if (key === 'amountPaid') return expectedAmount; @@ -364,7 +364,7 @@ describe('Testing Impact Service', () => { }); describe('Error handling', () => { - it('When user settings are missing, then it handles them gracefully', async () => { + it('When the user profile is not available, then conversion tracking completes without crashing', async () => { const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.spyOn(localStorageService, 'getUser').mockReturnValue(null); @@ -373,13 +373,13 @@ describe('Testing Impact Service', () => { consoleWarnSpy.mockRestore(); }); - it('When gtag is not available, then it continues execution', async () => { + it('When Google Analytics is not loaded, then the rest of conversion tracking still runs', async () => { globalThis.window.gtag = undefined as any; await expect(trackPaymentConversion()).resolves.not.toThrow(); }); - it('When an error occurs in the entire function, then it handles it gracefully', async () => { + it('When an unexpected error occurs, then conversion tracking fails silently without crashing', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.spyOn(localStorageService, 'getUser').mockImplementation(() => { throw new Error('Storage Error'); @@ -394,7 +394,7 @@ describe('Testing Impact Service', () => { }); describe('uuid library', () => { - it('When calling v4, then it generates a valid UUID', async () => { + it('When a UUID is generated, then it follows the expected UUID v4 format', async () => { const { v4 } = await vi.importActual('uuid'); const id = v4(); expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); From 204fbaeafde922bb80a20b163862c70a509065cf Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Tue, 12 May 2026 09:58:11 +0200 Subject: [PATCH 4/4] update impact sevice and test --- src/app/analytics/impact.service.test.ts | 21 --------------------- src/app/analytics/impact.service.ts | 8 ++++++-- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/app/analytics/impact.service.test.ts b/src/app/analytics/impact.service.test.ts index 3b7d8b1a40..fd164a9346 100644 --- a/src/app/analytics/impact.service.test.ts +++ b/src/app/analytics/impact.service.test.ts @@ -118,30 +118,10 @@ describe('Testing Impact Service', () => { expect(setToLocalStorageSpy).toHaveBeenCalledWith('amountPaid', expectedAmount); }); - it('When the plan is not lifetime, then it saves the subscription ID to localStorage', () => { - const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); - - savePaymentDataInLocalStorage({ - subscriptionId: subId, - paymentIntentId: undefined, - selectedPlan: product as PriceWithTax, - users: 1, - couponCodeData: promoCode, - isFirstPurchase: true, - }); - - expect(setToLocalStorageSpy).toHaveBeenCalledWith('subscriptionId', subId); - }); - it('When the plan is lifetime, then it saves the payment intent ID to localStorage', () => { const setToLocalStorageSpy = vi.spyOn(localStorageService, 'set'); - const lifetimeProduct = { - ...product, - price: { ...product.price, interval: 'lifetime' }, - }; savePaymentDataInLocalStorage({ - subscriptionId: undefined, paymentIntentId, selectedPlan: product as PriceWithTax, users: 1, @@ -374,7 +354,6 @@ describe('Testing Impact Service', () => { vi.spyOn(localStorageService, 'get').mockImplementation((key) => { if (key === 'couponCode') return 'CNINTERNXT'; // In whitelist if (key === 'amountPaid') return expectedAmount; - if (key === 'subscriptionId') return subId; if (key === 'isFirstPurchase') return 'true'; return null; }); diff --git a/src/app/analytics/impact.service.ts b/src/app/analytics/impact.service.ts index e9d6ca3424..6549b3741e 100644 --- a/src/app/analytics/impact.service.ts +++ b/src/app/analytics/impact.service.ts @@ -70,12 +70,13 @@ export async function trackSignUp(uuid: string): Promise { const IMPACT_API = envService.getVariable('impactApiUrl'); const anonymousID = getCookie('impactAnonymousId'); const source = getCookie('impactSource'); + const irclickid = getCookie('impactClickId'); if (globalThis.window.gtag) { window.gtag('event', 'User Signup'); } - if (source && source !== 'direct') { + if ((source && source !== 'direct') || irclickid) { await axios.post(IMPACT_API, { anonymousId: anonymousID, timestamp: dayjs().format('YYYY-MM-DDTHH:mm:ss.sssZ'), @@ -84,6 +85,7 @@ export async function trackSignUp(uuid: string): Promise { type: 'track', event: 'User Signup', ...(gclid && { gclid }), + ...(irclickid && { properties: { irclickid } }), }); } } catch (error) { @@ -123,11 +125,12 @@ export async function trackPaymentConversion(): Promise { const IMPACT_API = envService.getVariable('impactApiUrl'); const anonymousID = getCookie('impactAnonymousId') || uuidV4(); const source = getCookie('impactSource'); + const irclickid = getCookie('impactClickId'); const IMPACT_COUPON_WHITELIST = ['CNINTERNXT', 'CNINTERNXTL', 'CLOUDOFF']; const isImpactCoupon = couponCode && IMPACT_COUPON_WHITELIST.includes(couponCode.toUpperCase()); - if (isFirstPurchase && ((source && source !== 'direct') || isImpactCoupon)) { + if (isFirstPurchase && ((source && source !== 'direct') || isImpactCoupon || irclickid)) { try { await axios.post(IMPACT_API, { anonymousId: anonymousID, @@ -136,6 +139,7 @@ export async function trackPaymentConversion(): Promise { impact_value: amount === 0 ? 0.01 : amount, payment_intent: paymentIntent, ...(couponCode && { order_promo_code: couponCode }), + ...(irclickid && { irclickid }), }, userId: uuid, type: 'track',