Skip to content

Commit 4fb386d

Browse files
committed
fix: avoid redundant checkout attribution persistence
1 parent eccb19e commit 4fb386d

File tree

3 files changed

+60
-22
lines changed

3 files changed

+60
-22
lines changed

src/platform/telemetry/types.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,7 @@ export interface PageViewMetadata {
281281
[key: string]: unknown
282282
}
283283

284-
export interface BeginCheckoutMetadata extends Record<string, unknown> {
285-
user_id: string
286-
tier: TierKey
287-
cycle: BillingCycle
288-
checkout_type: 'new' | 'change'
289-
previous_tier?: TierKey
284+
export interface CheckoutAttributionMetadata {
290285
ga_client_id?: string
291286
ga_session_id?: string
292287
ga_session_number?: string
@@ -302,6 +297,15 @@ export interface BeginCheckoutMetadata extends Record<string, unknown> {
302297
wbraid?: string
303298
}
304299

300+
export interface BeginCheckoutMetadata
301+
extends Record<string, unknown>, CheckoutAttributionMetadata {
302+
user_id: string
303+
tier: TierKey
304+
cycle: BillingCycle
305+
checkout_type: 'new' | 'change'
306+
previous_tier?: TierKey
307+
}
308+
305309
/**
306310
* Telemetry provider interface for individual providers.
307311
* All methods are optional - providers only implement what they need.

src/platform/telemetry/utils/__tests__/checkoutAttribution.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,32 @@ describe('getCheckoutAttribution', () => {
104104
im_ref: 'impact-789'
105105
})
106106
})
107+
108+
it('does not persist when explicit search attribution matches stored values', () => {
109+
storage.set(
110+
'comfy_checkout_attribution',
111+
JSON.stringify({ utm_source: 'impact', im_ref: 'impact-789' })
112+
)
113+
114+
captureCheckoutAttributionFromSearch('?utm_source=impact&im_ref=impact-789')
115+
116+
expect(mockLocalStorage.setItem).not.toHaveBeenCalled()
117+
})
118+
119+
it('does not persist from URL when query attribution matches stored values', () => {
120+
storage.set(
121+
'comfy_checkout_attribution',
122+
JSON.stringify({ gclid: 'gclid-123', im_ref: 'impact-abc' })
123+
)
124+
window.history.pushState({}, '', '/?gclid=gclid-123&im_ref=impact-abc')
125+
126+
const attribution = getCheckoutAttribution()
127+
128+
expect(attribution).toMatchObject({
129+
gclid: 'gclid-123',
130+
im_ref: 'impact-abc',
131+
impact_click_id: 'impact-abc'
132+
})
133+
expect(mockLocalStorage.setItem).not.toHaveBeenCalled()
134+
})
107135
})

src/platform/telemetry/utils/checkoutAttribution.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import { isPlainObject } from 'es-toolkit'
22

3-
interface CheckoutAttribution {
4-
ga_client_id?: string
5-
ga_session_id?: string
6-
ga_session_number?: string
7-
im_ref?: string
8-
impact_click_id?: string
9-
utm_source?: string
10-
utm_medium?: string
11-
utm_campaign?: string
12-
utm_term?: string
13-
utm_content?: string
14-
gclid?: string
15-
gbraid?: string
16-
wbraid?: string
17-
}
3+
import type { CheckoutAttributionMetadata } from '../types'
4+
5+
type CheckoutAttribution = CheckoutAttributionMetadata
186

197
type GaIdentity = {
208
client_id?: string
@@ -68,6 +56,20 @@ function persistAttribution(
6856
}
6957
}
7058

59+
function hasAttributionChanges(
60+
existing: Partial<Record<AttributionQueryKey, string>>,
61+
incoming: Partial<Record<AttributionQueryKey, string>>
62+
): boolean {
63+
for (const key of ATTRIBUTION_QUERY_KEYS) {
64+
const value = incoming[key]
65+
if (value !== undefined && existing[key] !== value) {
66+
return true
67+
}
68+
}
69+
70+
return false
71+
}
72+
7173
function readAttributionFromUrl(
7274
search: string
7375
): Partial<Record<AttributionQueryKey, string>> {
@@ -106,6 +108,7 @@ export function captureCheckoutAttributionFromSearch(search: string): void {
106108
const stored = readStoredAttribution()
107109
const fromSearch = readAttributionFromUrl(search)
108110
if (Object.keys(fromSearch).length === 0) return
111+
if (!hasAttributionChanges(stored, fromSearch)) return
109112

110113
persistAttribution({
111114
...stored,
@@ -123,7 +126,10 @@ export function getCheckoutAttribution(): CheckoutAttribution {
123126
...fromUrl
124127
}
125128

126-
if (Object.keys(fromUrl).length > 0) {
129+
if (
130+
Object.keys(fromUrl).length > 0 &&
131+
hasAttributionChanges(stored, fromUrl)
132+
) {
127133
persistAttribution(merged)
128134
}
129135

0 commit comments

Comments
 (0)