Files
ComfyUI_frontend/global.d.ts
nav-tej e4d5824813 feat(telemetry): capture Rewardful referral on checkout attribution (#12311)
*PR Created by the Glary-Bot Agent*

---

## Summary

Mirrors the existing Impact affiliate wiring for the new Rewardful
affiliate network. The client reads `window.Rewardful.referral` when
`getCheckoutAttribution()` runs at checkout time and emits it as a new
optional `rewardful_referral` field on `CheckoutAttributionMetadata`.
The Go backend consumes this field separately and passes it to Stripe as
`ClientReferenceID` on the Checkout Session create call — that wiring
lives in a sibling PR on `Comfy-Org/cloud` (services/comfy-api) and is
the path that actually credits affiliate commissions for Stripe
subscriptions.

Per Rewardful's docs for server-side Stripe Checkout Sessions, the
GTM-loaded Rewardful JS handles cookie persistence on the client but
**cannot** attribute Checkout Sessions on its own — the merchant must
explicitly pass the referral UUID server-side as `client_reference_id`.

## 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 simply omitted from the payload.
- Adds a narrow `RewardfulGlobal` interface to `global.d.ts` (`referral`
plus optional `affiliate`/`campaign` metadata Rewardful exposes) so
`window.Rewardful` is typed everywhere it's accessed.
- 4 new unit tests covering: present, absent, empty-string, and
alongside Impact attribution. The existing 10 Impact/UTM tests are
untouched.

## Files touched

| File | Change |
|---|---|
| `global.d.ts` | Add `RewardfulGlobal` interface + `Rewardful?:` on
`Window` |
| `src/platform/telemetry/types.ts` | Add `rewardful_referral?: string`
to `CheckoutAttributionMetadata` |
| `src/platform/telemetry/utils/checkoutAttribution.ts` | Read
`window.Rewardful?.referral` in `getCheckoutAttribution()` |
| `src/platform/telemetry/utils/__tests__/checkoutAttribution.test.ts` |
4 new tests + `window.Rewardful = undefined` reset in `beforeEach` |

## Cross-PR dependency

Needs the sibling [`Comfy-Org/cloud`
PR](https://github.com/Comfy-Org/cloud/pulls?q=is%3Apr+rewardful)
(branch `glary/rewardful-affiliate-tracking`) 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.

## Verification

- `pnpm typecheck` — clean
- `pnpm test:unit src/platform/telemetry/utils` — 14/14 passing (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 unrelated warning)
- `pnpm exec vite build` — successful (7.85s)

Related: FE-704 (Finish affiliate pages PRs) for context on the broader
affiliate launch.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12311-feat-telemetry-capture-Rewardful-referral-on-checkout-attribution-3626d73d365081beb03afb2e000c83a6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: glary-bot <glary-bot@comfy.org>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
2026-06-03 17:40:28 +00:00

93 lines
2.5 KiB
TypeScript

declare const __COMFYUI_FRONTEND_VERSION__: string
declare const __COMFYUI_FRONTEND_COMMIT__: string
declare const __SENTRY_ENABLED__: boolean
declare const __SENTRY_DSN__: string
declare const __ALGOLIA_APP_ID__: string
declare const __ALGOLIA_API_KEY__: string
declare const __USE_PROD_CONFIG__: boolean
interface ImpactQueueFunction {
(...args: unknown[]): void
a?: unknown[][]
}
interface RewardfulGlobal {
referral?: string
affiliate?: { id?: string; token?: string; name?: string }
campaign?: { id?: string; name?: string }
}
interface RewardfulQueueFunction {
(method: 'ready', callback: () => void): void
(...args: unknown[]): void
q?: unknown[][]
}
type GtagGetFieldName = 'client_id' | 'session_id' | 'session_number'
interface GtagGetFieldValueMap {
client_id: string | number | undefined
session_id: string | number | undefined
session_number: string | number | undefined
}
interface GtagFunction {
<TField extends GtagGetFieldName>(
command: 'get',
targetId: string,
fieldName: TField,
callback: (value: GtagGetFieldValueMap[TField]) => void
): void
(...args: unknown[]): void
}
interface Window {
__CONFIG__: {
gtm_container_id?: string
ga_measurement_id?: string
mixpanel_token?: string
posthog_project_token?: string
posthog_api_host?: string
posthog_config?: Record<string, unknown>
require_whitelist?: boolean
subscription_required?: boolean
max_upload_size?: number
comfy_api_base_url?: string
comfy_platform_base_url?: string
firebase_config?: {
apiKey: string
authDomain: string
databaseURL?: string
projectId: string
storageBucket: string
messagingSenderId: string
appId: string
measurementId?: string
}
server_health_alert?: {
message: string
tooltip?: string
severity?: 'info' | 'warning' | 'error'
badge?: string
}
}
dataLayer?: Array<Record<string, unknown>>
gtag?: GtagFunction
ire_o?: string
ire?: ImpactQueueFunction
rewardful?: RewardfulQueueFunction
Rewardful?: RewardfulGlobal
}
interface Navigator {
/**
* Used by the electron API. This is a WICG non-standard API, but is guaranteed to exist in Electron.
* It is `undefined` in Firefox and older browsers.
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/windowControlsOverlay
*/
windowControlsOverlay?: {
/** When `true`, the window is using custom window style. */
visible: boolean
}
}