mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 13:32:11 +00:00
feat(telemetry): capture Rewardful referral on checkout attribution
Mirrors the existing Impact wiring for the new Rewardful affiliate network: client reads window.Rewardful.referral when getCheckoutAttribution runs, and emits it as a new optional rewardful_referral field on CheckoutAttributionMetadata. The Go backend (comfy-api) consumes this field separately and passes it to Stripe as ClientReferenceID on the CheckoutSession create call — that wiring lives in a sibling PR on Comfy-Org/comfy-api and is the path that actually credits affiliate commissions for Stripe subscriptions (per Rewardful docs, GTM-loaded JS alone cannot attribute Checkout Sessions; the merchant must pass the referral UUID server-side). Why this is the simplest possible client-side change: - Rewardful's JS (loaded via GTM) owns its own cookie persistence, so unlike Impact (where we capture im_ref from URL params and persist to localStorage ourselves) we just read window.Rewardful.referral at checkout time. No URL fallback, no localStorage handling. If Rewardful's script hasn't loaded or the user didn't come from an affiliate link, the field is omitted from the payload entirely. - Adds a narrow RewardfulGlobal interface to global.d.ts (referral plus optional affiliate/campaign metadata Rewardful exposes) so window.Rewardful is typed everywhere. - Adds 4 unit tests covering: present, absent, empty-string, and alongside Impact attribution. The existing 10 Impact/UTM tests are untouched. Verified (locally on the workspace clone): - pnpm typecheck — clean - pnpm test:unit src/platform/telemetry/utils — 14/14 (10 prior + 4 new) - pnpm test:unit (full repo) — passing - pnpm lint — 3 warnings, 0 errors (warnings pre-existing on main) - pnpm format:check — clean - pnpm knip — clean (1 pre-existing warning unrelated) - pnpm exec vite build — successful (7.85s) Cross-PR dependency: needs the sibling comfy-api PR to actually credit referrals. This PR is safe to ship independently — the field is just ignored by the existing comfy-api endpoint until that PR lands.
This commit is contained in:
7
global.d.ts
vendored
7
global.d.ts
vendored
@@ -11,6 +11,12 @@ interface ImpactQueueFunction {
|
||||
a?: unknown[][]
|
||||
}
|
||||
|
||||
interface RewardfulGlobal {
|
||||
referral?: string
|
||||
affiliate?: { id?: string; token?: string; name?: string }
|
||||
campaign?: { id?: string; name?: string }
|
||||
}
|
||||
|
||||
type GtagGetFieldName = 'client_id' | 'session_id' | 'session_number'
|
||||
|
||||
interface GtagGetFieldValueMap {
|
||||
@@ -63,6 +69,7 @@ interface Window {
|
||||
gtag?: GtagFunction
|
||||
ire_o?: string
|
||||
ire?: ImpactQueueFunction
|
||||
Rewardful?: RewardfulGlobal
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
|
||||
@@ -325,6 +325,7 @@ export interface CheckoutAttributionMetadata {
|
||||
ga_session_id?: string
|
||||
ga_session_number?: string
|
||||
im_ref?: string
|
||||
rewardful_referral?: string
|
||||
utm_source?: string
|
||||
utm_medium?: string
|
||||
utm_campaign?: string
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('getCheckoutAttribution', () => {
|
||||
}
|
||||
window.gtag = undefined
|
||||
window.ire = undefined
|
||||
window.Rewardful = undefined
|
||||
window.history.pushState({}, '', '/')
|
||||
})
|
||||
|
||||
@@ -228,4 +229,47 @@ describe('getCheckoutAttribution', () => {
|
||||
|
||||
expect(attribution.im_ref).toBeUndefined()
|
||||
})
|
||||
|
||||
it('captures Rewardful referral from window.Rewardful', async () => {
|
||||
window.Rewardful = {
|
||||
referral: 'rwd-abc-123'
|
||||
}
|
||||
|
||||
const attribution = await getCheckoutAttribution()
|
||||
|
||||
expect(attribution.rewardful_referral).toBe('rwd-abc-123')
|
||||
})
|
||||
|
||||
it('returns undefined Rewardful referral when window.Rewardful is absent', async () => {
|
||||
const attribution = await getCheckoutAttribution()
|
||||
|
||||
expect(attribution.rewardful_referral).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns undefined Rewardful referral when window.Rewardful.referral is empty', async () => {
|
||||
window.Rewardful = { referral: '' }
|
||||
|
||||
const attribution = await getCheckoutAttribution()
|
||||
|
||||
expect(attribution.rewardful_referral).toBeUndefined()
|
||||
})
|
||||
|
||||
it('captures Rewardful referral alongside Impact attribution', async () => {
|
||||
window.history.pushState(
|
||||
{},
|
||||
'',
|
||||
'/?im_ref=impact-url-id&utm_source=affiliate'
|
||||
)
|
||||
window.Rewardful = {
|
||||
referral: 'rwd-xyz-789'
|
||||
}
|
||||
|
||||
const attribution = await getCheckoutAttribution()
|
||||
|
||||
expect(attribution).toMatchObject({
|
||||
im_ref: 'impact-url-id',
|
||||
utm_source: 'affiliate',
|
||||
rewardful_referral: 'rwd-xyz-789'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -180,6 +180,11 @@ async function getGeneratedClickId(): Promise<string | undefined> {
|
||||
}
|
||||
}
|
||||
|
||||
function getRewardfulReferral(): string | undefined {
|
||||
if (typeof window === 'undefined') return undefined
|
||||
return asNonEmptyString(window.Rewardful?.referral)
|
||||
}
|
||||
|
||||
export function captureCheckoutAttributionFromSearch(search: string): void {
|
||||
const fromUrl = readAttributionFromUrl(search)
|
||||
const storedAttribution = readStoredAttribution()
|
||||
@@ -213,11 +218,13 @@ export async function getCheckoutAttribution(): Promise<CheckoutAttributionMetad
|
||||
}
|
||||
|
||||
const gaIdentity = await getGaIdentity()
|
||||
const rewardfulReferral = getRewardfulReferral()
|
||||
|
||||
return {
|
||||
...attribution,
|
||||
ga_client_id: gaIdentity?.client_id,
|
||||
ga_session_id: gaIdentity?.session_id,
|
||||
ga_session_number: gaIdentity?.session_number
|
||||
ga_session_number: gaIdentity?.session_number,
|
||||
rewardful_referral: rewardfulReferral
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user