@tagadapay/plugin-sdk 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1129 -1129
- package/build-cdn.js +499 -499
- package/dist/external-tracker.js +156 -2
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/tagada-react-sdk-minimal.min.js +2 -2
- package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
- package/dist/tagada-react-sdk.js +696 -245
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +2908 -94
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/funnelClient.d.ts +40 -0
- package/dist/v2/core/funnelClient.js +30 -0
- package/dist/v2/core/pixelTracker.d.ts +51 -0
- package/dist/v2/core/pixelTracker.js +425 -0
- package/dist/v2/core/resources/checkout.d.ts +45 -1
- package/dist/v2/core/resources/checkout.js +13 -3
- package/dist/v2/core/resources/offers.d.ts +1 -1
- package/dist/v2/core/resources/offers.js +3 -1
- package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
- package/dist/v2/core/resources/promotionEvents.js +2 -0
- package/dist/v2/core/resources/promotions.d.ts +6 -1
- package/dist/v2/core/resources/promotions.js +6 -1
- package/dist/v2/core/resources/shippingRates.d.ts +18 -0
- package/dist/v2/core/resources/shippingRates.js +18 -0
- package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
- package/dist/v2/core/utils/clickIdResolver.js +169 -0
- package/dist/v2/core/utils/index.d.ts +2 -0
- package/dist/v2/core/utils/index.js +4 -0
- package/dist/v2/core/utils/metaEventId.d.ts +14 -0
- package/dist/v2/core/utils/metaEventId.js +16 -0
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +7 -0
- package/dist/v2/index.js +10 -0
- package/dist/v2/react/components/ApplePayButton.js +50 -0
- package/dist/v2/react/components/FunnelScriptInjector.js +9 -9
- package/dist/v2/react/components/GooglePayButton.js +39 -1
- package/dist/v2/react/components/StripeExpressButton.js +54 -2
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +11 -11
- package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
- package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
- package/dist/v2/react/hooks/useFunnel.js +2 -1
- package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
- package/dist/v2/react/hooks/usePixelTracking.js +32 -374
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
- package/dist/v2/react/hooks/usePreviewOffer.js +8 -2
- package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
- package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
- package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
- package/dist/v2/react/hooks/useStepConfig.js +5 -1
- package/dist/v2/react/index.d.ts +4 -0
- package/dist/v2/react/index.js +8 -0
- package/dist/v2/react/providers/TagadaProvider.js +18 -5
- package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
- package/dist/v2/standalone/index.d.ts +3 -0
- package/dist/v2/standalone/index.js +23 -0
- package/dist/v2/standalone/payment-service.d.ts +54 -1
- package/dist/v2/standalone/payment-service.js +228 -61
- package/package.json +115 -115
|
@@ -164,6 +164,13 @@ export interface RuntimeStepConfig {
|
|
|
164
164
|
mode: 'inherit' | 'custom';
|
|
165
165
|
enabledUpsellIds?: string[];
|
|
166
166
|
};
|
|
167
|
+
/** In-step tiered offer selector (swaps cart line items on customer pick).
|
|
168
|
+
* Backed by the `offers` table; ids reference `offers.id` rows. */
|
|
169
|
+
checkoutOffers?: {
|
|
170
|
+
mode: 'inherit' | 'custom';
|
|
171
|
+
enabledOfferIds?: string[];
|
|
172
|
+
defaultOfferId?: string;
|
|
173
|
+
};
|
|
167
174
|
/** @deprecated Use `resources` instead */
|
|
168
175
|
staticResources?: Record<string, string>;
|
|
169
176
|
/** Typed resource bindings (replaces staticResources) */
|
|
@@ -175,6 +182,21 @@ export interface RuntimeStepConfig {
|
|
|
175
182
|
position?: 'head-start' | 'head-end' | 'body-start' | 'body-end';
|
|
176
183
|
}>;
|
|
177
184
|
pixels?: PixelsConfig;
|
|
185
|
+
/** Per-step shipping rate hide-list. Listed IDs are hidden; everything else visible. */
|
|
186
|
+
shippingRateSettings?: {
|
|
187
|
+
hiddenShippingRateIds?: string[];
|
|
188
|
+
};
|
|
189
|
+
/** Per-step address form config. */
|
|
190
|
+
addressSettings?: {
|
|
191
|
+
/** ISO-3166-1 alpha-2 codes. Empty/missing = all countries allowed. */
|
|
192
|
+
countryAllowlist?: string[];
|
|
193
|
+
/** ISO-3166-1 alpha-2. Applied on initial load only, overrides geolocation. */
|
|
194
|
+
forcedCountry?: string;
|
|
195
|
+
};
|
|
196
|
+
/** Payment initiator for upsell/offer charges. MIT = merchant initiated
|
|
197
|
+
* (off-session, no 3DS). CIT = customer initiated (may trigger 3DS).
|
|
198
|
+
* Default = 'merchant' (MIT). */
|
|
199
|
+
paymentInitiator?: 'merchant' | 'customer';
|
|
178
200
|
}
|
|
179
201
|
/**
|
|
180
202
|
* Local funnel configuration for development
|
|
@@ -199,6 +221,8 @@ export interface LocalFunnelConfig {
|
|
|
199
221
|
orderBumps?: RuntimeStepConfig['orderBumps'];
|
|
200
222
|
/** Upsell offer filtering config for local testing */
|
|
201
223
|
upsellOffers?: RuntimeStepConfig['upsellOffers'];
|
|
224
|
+
/** Checkout offer selector config for local testing */
|
|
225
|
+
checkoutOffers?: RuntimeStepConfig['checkoutOffers'];
|
|
202
226
|
}
|
|
203
227
|
/**
|
|
204
228
|
* Load local funnel config from /config/funnel.local.json (for local dev only)
|
|
@@ -243,6 +267,11 @@ export declare function getAssignedStepConfig(): RuntimeStepConfig | undefined;
|
|
|
243
267
|
* 4. meta[name="x-payment-flow-id"] (legacy meta tag)
|
|
244
268
|
*/
|
|
245
269
|
export declare function getAssignedPaymentFlowId(): string | undefined;
|
|
270
|
+
/**
|
|
271
|
+
* Get the payment initiator override from step config.
|
|
272
|
+
* Returns 'merchant' (MIT) or 'customer' (CIT) — undefined means "use default".
|
|
273
|
+
*/
|
|
274
|
+
export declare function getAssignedPaymentInitiator(): 'merchant' | 'customer' | undefined;
|
|
246
275
|
/**
|
|
247
276
|
* Get resource bindings from step config.
|
|
248
277
|
* Reads from `resources` (new) with `staticResources` (legacy) as fallback, merged.
|
|
@@ -271,6 +300,17 @@ export declare function getAssignedOrderBumpOfferIds(): string[] | undefined;
|
|
|
271
300
|
* Returns the explicit list when mode is 'custom'.
|
|
272
301
|
*/
|
|
273
302
|
export declare function getAssignedUpsellOfferIds(): string[] | undefined;
|
|
303
|
+
/**
|
|
304
|
+
* Get the enabled in-step checkout-offer IDs (offers.id rows) for this step.
|
|
305
|
+
* Returns undefined when mode is 'inherit' (surface all store offers).
|
|
306
|
+
* Returns the explicit ordered list when mode is 'custom'.
|
|
307
|
+
*/
|
|
308
|
+
export declare function getAssignedCheckoutOfferIds(): string[] | undefined;
|
|
309
|
+
/**
|
|
310
|
+
* Get the default checkout-offer ID (offers.id) configured for this step.
|
|
311
|
+
* Returns undefined when no default is configured.
|
|
312
|
+
*/
|
|
313
|
+
export declare function getAssignedCheckoutOfferDefault(): string | undefined;
|
|
274
314
|
export interface FunnelClientConfig {
|
|
275
315
|
apiClient: ApiClient;
|
|
276
316
|
debugMode?: boolean;
|
|
@@ -226,6 +226,7 @@ function localConfigToStepConfig(local) {
|
|
|
226
226
|
pixels: local.pixels,
|
|
227
227
|
orderBumps: local.orderBumps,
|
|
228
228
|
upsellOffers: local.upsellOffers,
|
|
229
|
+
checkoutOffers: local.checkoutOffers,
|
|
229
230
|
};
|
|
230
231
|
}
|
|
231
232
|
/**
|
|
@@ -300,6 +301,14 @@ export function getAssignedPaymentFlowId() {
|
|
|
300
301
|
}
|
|
301
302
|
return undefined;
|
|
302
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Get the payment initiator override from step config.
|
|
306
|
+
* Returns 'merchant' (MIT) or 'customer' (CIT) — undefined means "use default".
|
|
307
|
+
*/
|
|
308
|
+
export function getAssignedPaymentInitiator() {
|
|
309
|
+
const stepConfig = getAssignedStepConfig();
|
|
310
|
+
return stepConfig?.paymentInitiator;
|
|
311
|
+
}
|
|
303
312
|
/**
|
|
304
313
|
* Get resource bindings from step config.
|
|
305
314
|
* Reads from `resources` (new) with `staticResources` (legacy) as fallback, merged.
|
|
@@ -401,6 +410,27 @@ export function getAssignedUpsellOfferIds() {
|
|
|
401
410
|
return undefined;
|
|
402
411
|
return stepConfig.upsellOffers.enabledUpsellIds;
|
|
403
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Get the enabled in-step checkout-offer IDs (offers.id rows) for this step.
|
|
415
|
+
* Returns undefined when mode is 'inherit' (surface all store offers).
|
|
416
|
+
* Returns the explicit ordered list when mode is 'custom'.
|
|
417
|
+
*/
|
|
418
|
+
export function getAssignedCheckoutOfferIds() {
|
|
419
|
+
const stepConfig = getAssignedStepConfig();
|
|
420
|
+
if (!stepConfig?.checkoutOffers)
|
|
421
|
+
return undefined;
|
|
422
|
+
if (stepConfig.checkoutOffers.mode !== 'custom')
|
|
423
|
+
return undefined;
|
|
424
|
+
return stepConfig.checkoutOffers.enabledOfferIds;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get the default checkout-offer ID (offers.id) configured for this step.
|
|
428
|
+
* Returns undefined when no default is configured.
|
|
429
|
+
*/
|
|
430
|
+
export function getAssignedCheckoutOfferDefault() {
|
|
431
|
+
const stepConfig = getAssignedStepConfig();
|
|
432
|
+
return stepConfig?.checkoutOffers?.defaultOfferId;
|
|
433
|
+
}
|
|
404
434
|
export class FunnelClient {
|
|
405
435
|
constructor(config) {
|
|
406
436
|
this.eventDispatcher = new EventDispatcher();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic pixel tracker.
|
|
3
|
+
*
|
|
4
|
+
* Init / fire logic for Meta, TikTok, Snapchat, Pinterest, GTM/GA4 pixels —
|
|
5
|
+
* extracted from `react/hooks/usePixelTracking.tsx` so non-React entry points
|
|
6
|
+
* (Studio islands hydrator, full-SPA runtime) can fire pixel events from
|
|
7
|
+
* `window.__TGD_STEP_CONFIG__.pixels`.
|
|
8
|
+
*
|
|
9
|
+
* The React provider is a thin adapter on top of this module.
|
|
10
|
+
*/
|
|
11
|
+
import type { PixelsConfig } from './funnelClient';
|
|
12
|
+
import { type StandardPixelEvent } from './pixelMapping';
|
|
13
|
+
/**
|
|
14
|
+
* Optional per-event tracking metadata. `eventId` is the cross-channel dedup
|
|
15
|
+
* key that the SDK threads to `fbq` (Meta) and `ttq` (TikTok) so the same
|
|
16
|
+
* eventID/event_id can be matched against a server-side CAPI event. Use the
|
|
17
|
+
* `makeMetaEventId(eventName, entityId)` helper from `core/utils/metaEventId`
|
|
18
|
+
* to keep the formula in lockstep with the server.
|
|
19
|
+
*/
|
|
20
|
+
export interface TrackOptions {
|
|
21
|
+
eventId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PixelTracker {
|
|
24
|
+
/** Resolves once all pixel scripts have loaded (or hit their load timeout). */
|
|
25
|
+
readonly initPromise: Promise<void>;
|
|
26
|
+
/** True after `initPromise` resolves. */
|
|
27
|
+
readonly pixelsInitialized: boolean;
|
|
28
|
+
/** Track a standard pixel event. No-ops until init resolves. */
|
|
29
|
+
track(eventName: StandardPixelEvent, parameters?: Record<string, unknown>, options?: TrackOptions): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a pixel tracker for a given pixels config.
|
|
33
|
+
* Kicks off pixel script init immediately, fires PageView once init resolves,
|
|
34
|
+
* and returns a `track()` function that no-ops until init completes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createPixelTracker(pixels: PixelsConfig | undefined): PixelTracker;
|
|
37
|
+
declare global {
|
|
38
|
+
interface Window {
|
|
39
|
+
__TGD_PIXEL_TRACKER__?: PixelTracker;
|
|
40
|
+
__TGD_STEP_CONFIG__?: string | Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One-shot bootstrap: reads `window.__TGD_STEP_CONFIG__.pixels`, creates a
|
|
45
|
+
* tracker, kicks off init, and stashes it on `window.__TGD_PIXEL_TRACKER__`.
|
|
46
|
+
*
|
|
47
|
+
* Idempotent — both the islands SDK and the full-SPA runtime may call this,
|
|
48
|
+
* but only the first call creates a tracker. Returns the active tracker
|
|
49
|
+
* (existing or newly created), or null if there's no window or no pixels.
|
|
50
|
+
*/
|
|
51
|
+
export declare function bootstrapPixelTrackerFromWindow(): PixelTracker | null;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic pixel tracker.
|
|
3
|
+
*
|
|
4
|
+
* Init / fire logic for Meta, TikTok, Snapchat, Pinterest, GTM/GA4 pixels —
|
|
5
|
+
* extracted from `react/hooks/usePixelTracking.tsx` so non-React entry points
|
|
6
|
+
* (Studio islands hydrator, full-SPA runtime) can fire pixel events from
|
|
7
|
+
* `window.__TGD_STEP_CONFIG__.pixels`.
|
|
8
|
+
*
|
|
9
|
+
* The React provider is a thin adapter on top of this module.
|
|
10
|
+
*/
|
|
11
|
+
import { resolvePixelEvents, } from './pixelMapping';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Duplicate guard (time-window based)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function createDuplicateGuard(windowMs) {
|
|
16
|
+
const lastEvents = new Map();
|
|
17
|
+
return (eventName, parameters, eventId) => {
|
|
18
|
+
try {
|
|
19
|
+
// Include eventId in the dedup key so two distinct logical events with the
|
|
20
|
+
// same name+params (e.g. two Purchases) are not collapsed by the guard.
|
|
21
|
+
const key = JSON.stringify({ eventName, parameters, eventId });
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const last = lastEvents.get(key);
|
|
24
|
+
if (last && now - last < windowMs)
|
|
25
|
+
return false;
|
|
26
|
+
lastEvents.set(key, now);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Browser-specific: fire an event to the correct global pixel function
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
38
|
+
function fire(provider, name, params, pixel, options = {}) {
|
|
39
|
+
if (typeof window === 'undefined')
|
|
40
|
+
return;
|
|
41
|
+
const w = window;
|
|
42
|
+
const eventId = options.eventId;
|
|
43
|
+
switch (provider) {
|
|
44
|
+
case 'facebook':
|
|
45
|
+
// Meta dedup: pass eventID as the 4th-arg `eventID` option so this
|
|
46
|
+
// browser-pixel event matches a server-side CAPI event with the same id.
|
|
47
|
+
// See https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events
|
|
48
|
+
if (eventId)
|
|
49
|
+
w.fbq?.('track', name, params, { eventID: eventId });
|
|
50
|
+
else
|
|
51
|
+
w.fbq?.('track', name, params);
|
|
52
|
+
break;
|
|
53
|
+
case 'tiktok': {
|
|
54
|
+
// Use ttq.instance(pixelId) to target each pixel individually,
|
|
55
|
+
// since the global ttq.track() only fires for the sdkid pixel.
|
|
56
|
+
const pixelId = pixel && 'pixelId' in pixel ? pixel.pixelId : null;
|
|
57
|
+
const target = pixelId && w.ttq?.instance ? w.ttq.instance(pixelId) : w.ttq;
|
|
58
|
+
if (name === 'Pageview') {
|
|
59
|
+
target?.page?.();
|
|
60
|
+
}
|
|
61
|
+
else if (eventId) {
|
|
62
|
+
target?.track?.(name, params, { event_id: eventId });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
target?.track?.(name, params);
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'snapchat':
|
|
70
|
+
w.snaptr?.('track', name, params);
|
|
71
|
+
break;
|
|
72
|
+
case 'pinterest':
|
|
73
|
+
// Pinterest handles page views via pintrk('page'), not pintrk('track', 'pagevisit')
|
|
74
|
+
if (name === 'pagevisit') {
|
|
75
|
+
w.pintrk?.('page');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
w.pintrk?.('track', name, params);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case 'gtm':
|
|
82
|
+
fireGTM(name, params);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function fireGTM(event, params) {
|
|
87
|
+
try {
|
|
88
|
+
const w = window;
|
|
89
|
+
if (w.gtag) {
|
|
90
|
+
w.gtag('event', event, params);
|
|
91
|
+
}
|
|
92
|
+
else if (w.dataLayer) {
|
|
93
|
+
w.dataLayer.push({ event, ...params });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error('[SDK GTM] Error:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
101
|
+
// ===========================================================================
|
|
102
|
+
// Pixel initialization (browser-only)
|
|
103
|
+
// All pixel globals are accessed via `win` typed as `any` to avoid
|
|
104
|
+
// `declare global` augmentation issues across tsconfig scopes.
|
|
105
|
+
// ===========================================================================
|
|
106
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
107
|
+
const win = (typeof window !== 'undefined' ? window : undefined);
|
|
108
|
+
const SCRIPT_LOAD_TIMEOUT_MS = 5000;
|
|
109
|
+
function waitForScriptLoad(script) {
|
|
110
|
+
// If the script has already loaded, resolve immediately
|
|
111
|
+
if (script.dataset.loaded)
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
const done = () => { script.dataset.loaded = '1'; clearTimeout(timer); resolve(); };
|
|
115
|
+
const timer = setTimeout(done, SCRIPT_LOAD_TIMEOUT_MS);
|
|
116
|
+
script.addEventListener('load', done, { once: true });
|
|
117
|
+
script.addEventListener('error', done, { once: true });
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
let _metaScriptEl = null;
|
|
121
|
+
function initMetaPixel(pixelId) {
|
|
122
|
+
if (!win)
|
|
123
|
+
return Promise.resolve();
|
|
124
|
+
// Initialize Meta base code once
|
|
125
|
+
if (!win.fbq) {
|
|
126
|
+
const n = function (...args) {
|
|
127
|
+
if (n.callMethod)
|
|
128
|
+
n.callMethod(...args);
|
|
129
|
+
else
|
|
130
|
+
n.queue.push(args);
|
|
131
|
+
};
|
|
132
|
+
n.queue = [];
|
|
133
|
+
n.loaded = true;
|
|
134
|
+
n.version = '2.0';
|
|
135
|
+
win.fbq = n;
|
|
136
|
+
if (!win._fbq)
|
|
137
|
+
win._fbq = n;
|
|
138
|
+
const t = document.createElement('script');
|
|
139
|
+
t.async = true;
|
|
140
|
+
t.src = 'https://connect.facebook.net/en_US/fbevents.js';
|
|
141
|
+
const s = document.getElementsByTagName('script')[0];
|
|
142
|
+
s?.parentNode?.insertBefore(t, s);
|
|
143
|
+
_metaScriptEl = t;
|
|
144
|
+
}
|
|
145
|
+
// Register each pixel ID (fbq supports multiple pixels via multiple init calls)
|
|
146
|
+
win.fbq('init', pixelId);
|
|
147
|
+
return _metaScriptEl ? waitForScriptLoad(_metaScriptEl) : Promise.resolve();
|
|
148
|
+
}
|
|
149
|
+
let _tiktokBaseInitialized = false;
|
|
150
|
+
function initTikTokPixel(pixelId) {
|
|
151
|
+
if (!win)
|
|
152
|
+
return Promise.resolve();
|
|
153
|
+
// Initialize TikTok base code once
|
|
154
|
+
if (!_tiktokBaseInitialized) {
|
|
155
|
+
_tiktokBaseInitialized = true;
|
|
156
|
+
win.TiktokAnalyticsObject = 'ttq';
|
|
157
|
+
const ttq = (win.ttq = win.ttq || []);
|
|
158
|
+
ttq.methods = [
|
|
159
|
+
'page', 'track', 'identify', 'instances', 'debug', 'on', 'off',
|
|
160
|
+
'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie',
|
|
161
|
+
'holdConsent', 'revokeConsent', 'grantConsent',
|
|
162
|
+
];
|
|
163
|
+
ttq.setAndDefer = function (t, e) {
|
|
164
|
+
t[e] = function (...args) { t.push([e, ...args]); };
|
|
165
|
+
};
|
|
166
|
+
for (const method of ttq.methods) {
|
|
167
|
+
ttq.setAndDefer(ttq, method);
|
|
168
|
+
}
|
|
169
|
+
ttq.instance = function (t) {
|
|
170
|
+
const e = ttq._i[t] || [];
|
|
171
|
+
for (const method of ttq.methods) {
|
|
172
|
+
ttq.setAndDefer(e, method);
|
|
173
|
+
}
|
|
174
|
+
return e;
|
|
175
|
+
};
|
|
176
|
+
ttq.load = function (e, n) {
|
|
177
|
+
const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
|
|
178
|
+
ttq._i = ttq._i || {};
|
|
179
|
+
ttq._i[e] = [];
|
|
180
|
+
ttq._i[e]._u = r;
|
|
181
|
+
ttq._t = ttq._t || {};
|
|
182
|
+
ttq._t[e] = +new Date();
|
|
183
|
+
ttq._o = ttq._o || {};
|
|
184
|
+
ttq._o[e] = n || {};
|
|
185
|
+
const s = document.createElement('script');
|
|
186
|
+
s.type = 'text/javascript';
|
|
187
|
+
s.async = true;
|
|
188
|
+
s.src = r + '?sdkid=' + e + '&lib=ttq';
|
|
189
|
+
const p = document.getElementsByTagName('script')[0];
|
|
190
|
+
p?.parentNode?.insertBefore(s, p);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Skip if this specific pixel ID is already registered
|
|
194
|
+
if (win.ttq._i?.[pixelId])
|
|
195
|
+
return Promise.resolve();
|
|
196
|
+
// Register pixel and load its script
|
|
197
|
+
win.ttq.load(pixelId);
|
|
198
|
+
// Find the script we just created and wait for it
|
|
199
|
+
const scripts = document.querySelectorAll('script[src*="analytics.tiktok.com/i18n/pixel/events.js"]');
|
|
200
|
+
const lastScript = scripts[scripts.length - 1];
|
|
201
|
+
return lastScript ? waitForScriptLoad(lastScript) : Promise.resolve();
|
|
202
|
+
}
|
|
203
|
+
let _snapchatScriptEl = null;
|
|
204
|
+
function initSnapchatPixel(pixelId) {
|
|
205
|
+
if (!win)
|
|
206
|
+
return Promise.resolve();
|
|
207
|
+
// Initialize Snapchat base code once
|
|
208
|
+
if (!win.snaptr) {
|
|
209
|
+
const a = function (...args) {
|
|
210
|
+
if (a.handleRequest)
|
|
211
|
+
a.handleRequest(...args);
|
|
212
|
+
else
|
|
213
|
+
a.queue.push(args);
|
|
214
|
+
};
|
|
215
|
+
a.queue = [];
|
|
216
|
+
win.snaptr = a;
|
|
217
|
+
const r = document.createElement('script');
|
|
218
|
+
r.async = true;
|
|
219
|
+
r.src = 'https://sc-static.net/scevent.min.js';
|
|
220
|
+
const u = document.getElementsByTagName('script')[0];
|
|
221
|
+
u?.parentNode?.insertBefore(r, u);
|
|
222
|
+
_snapchatScriptEl = r;
|
|
223
|
+
}
|
|
224
|
+
// Register each pixel ID (snaptr supports multiple pixels via multiple init calls)
|
|
225
|
+
win.snaptr('init', pixelId);
|
|
226
|
+
return _snapchatScriptEl ? waitForScriptLoad(_snapchatScriptEl) : Promise.resolve();
|
|
227
|
+
}
|
|
228
|
+
let _pinterestScriptEl = null;
|
|
229
|
+
function initPinterestPixel(pixelId) {
|
|
230
|
+
if (!win)
|
|
231
|
+
return Promise.resolve();
|
|
232
|
+
// Initialize Pinterest base code once
|
|
233
|
+
if (!win.pintrk) {
|
|
234
|
+
const a = function (...args) { a.queue.push(args); };
|
|
235
|
+
a.queue = [];
|
|
236
|
+
win.pintrk = a;
|
|
237
|
+
const s = document.createElement('script');
|
|
238
|
+
s.async = true;
|
|
239
|
+
s.src = 'https://s.pinimg.com/ct/core.js';
|
|
240
|
+
const u = document.getElementsByTagName('script')[0];
|
|
241
|
+
u?.parentNode?.insertBefore(s, u);
|
|
242
|
+
_pinterestScriptEl = s;
|
|
243
|
+
}
|
|
244
|
+
// Register each pixel ID (pintrk supports multiple pixels via multiple load calls)
|
|
245
|
+
win.pintrk('load', pixelId);
|
|
246
|
+
// Note: pintrk('page') is NOT called here — the auto page-view fires via fire().
|
|
247
|
+
return _pinterestScriptEl ? waitForScriptLoad(_pinterestScriptEl) : Promise.resolve();
|
|
248
|
+
}
|
|
249
|
+
function initGTM(containerId) {
|
|
250
|
+
if (!win || !containerId)
|
|
251
|
+
return Promise.resolve();
|
|
252
|
+
const isGtmContainer = containerId.startsWith('GTM-');
|
|
253
|
+
const scriptUrlPart = isGtmContainer
|
|
254
|
+
? `googletagmanager.com/gtm.js?id=${containerId}`
|
|
255
|
+
: `googletagmanager.com/gtag/js?id=${containerId}`;
|
|
256
|
+
if (document.querySelector(`script[src*="${scriptUrlPart}"]`))
|
|
257
|
+
return Promise.resolve();
|
|
258
|
+
win.dataLayer = win.dataLayer || [];
|
|
259
|
+
// Push referrer domain context into dataLayer before GTM loads, so tags
|
|
260
|
+
// inside the container can use it for cross-domain tracking configuration.
|
|
261
|
+
try {
|
|
262
|
+
const ref = document.referrer && new URL(document.referrer).hostname;
|
|
263
|
+
if (ref && ref !== window.location.hostname) {
|
|
264
|
+
win.dataLayer.push({ tagada_referrer_domain: ref });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch { /* ignore invalid referrer */ }
|
|
268
|
+
if (isGtmContainer) {
|
|
269
|
+
win.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
|
|
270
|
+
const f = document.getElementsByTagName('script')[0];
|
|
271
|
+
const j = document.createElement('script');
|
|
272
|
+
j.async = true;
|
|
273
|
+
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + containerId;
|
|
274
|
+
if (f?.parentNode)
|
|
275
|
+
f.parentNode.insertBefore(j, f);
|
|
276
|
+
else
|
|
277
|
+
(document.head || document.getElementsByTagName('head')[0])?.appendChild(j);
|
|
278
|
+
const noscript = document.createElement('noscript');
|
|
279
|
+
const iframe = document.createElement('iframe');
|
|
280
|
+
iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
|
|
281
|
+
iframe.height = '0';
|
|
282
|
+
iframe.width = '0';
|
|
283
|
+
iframe.style.display = 'none';
|
|
284
|
+
iframe.style.visibility = 'hidden';
|
|
285
|
+
noscript.appendChild(iframe);
|
|
286
|
+
(document.body || document.getElementsByTagName('body')[0])?.insertBefore(noscript, document.body?.firstChild ?? null);
|
|
287
|
+
return waitForScriptLoad(j);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
if (!win.gtag) {
|
|
291
|
+
win.gtag = function (..._args) { win.dataLayer.push(arguments); };
|
|
292
|
+
}
|
|
293
|
+
win.gtag('js', new Date());
|
|
294
|
+
// Enable cross-domain tracking: accept incoming _gl parameter from referring
|
|
295
|
+
// domains (e.g. Shopify store → TagadaPay checkout) so Google preserves the
|
|
296
|
+
// client-id/session across the redirect.
|
|
297
|
+
const linkerConfig = { accept_incoming: true };
|
|
298
|
+
try {
|
|
299
|
+
const ref = document.referrer && new URL(document.referrer).hostname;
|
|
300
|
+
if (ref && ref !== window.location.hostname) {
|
|
301
|
+
linkerConfig.domains = [ref, window.location.hostname];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch { /* ignore invalid referrer */ }
|
|
305
|
+
win.gtag('config', containerId, { linker: linkerConfig });
|
|
306
|
+
const script = document.createElement('script');
|
|
307
|
+
script.async = true;
|
|
308
|
+
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + containerId;
|
|
309
|
+
const firstScript = document.getElementsByTagName('script')[0];
|
|
310
|
+
if (firstScript?.parentNode)
|
|
311
|
+
firstScript.parentNode.insertBefore(script, firstScript);
|
|
312
|
+
else
|
|
313
|
+
(document.head || document.getElementsByTagName('head')[0])?.appendChild(script);
|
|
314
|
+
return waitForScriptLoad(script);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Tracker factory
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
/**
|
|
322
|
+
* Create a pixel tracker for a given pixels config.
|
|
323
|
+
* Kicks off pixel script init immediately, fires PageView once init resolves,
|
|
324
|
+
* and returns a `track()` function that no-ops until init completes.
|
|
325
|
+
*/
|
|
326
|
+
export function createPixelTracker(pixels) {
|
|
327
|
+
const shouldTrackEvent = createDuplicateGuard(5000);
|
|
328
|
+
let pixelsInitialized = false;
|
|
329
|
+
const initPromise = (async () => {
|
|
330
|
+
if (!pixels || typeof window === 'undefined')
|
|
331
|
+
return;
|
|
332
|
+
const loadPromises = [];
|
|
333
|
+
try {
|
|
334
|
+
pixels.facebook?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
335
|
+
loadPromises.push(initMetaPixel(px.pixelId)); });
|
|
336
|
+
pixels.tiktok?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
337
|
+
loadPromises.push(initTikTokPixel(px.pixelId)); });
|
|
338
|
+
pixels.snapchat?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
339
|
+
loadPromises.push(initSnapchatPixel(px.pixelId)); });
|
|
340
|
+
pixels.pinterest?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
341
|
+
loadPromises.push(initPinterestPixel(px.pixelId)); });
|
|
342
|
+
pixels.gtm?.forEach((px) => { if (px.enabled && px.containerId)
|
|
343
|
+
loadPromises.push(initGTM(px.containerId)); });
|
|
344
|
+
await Promise.all(loadPromises);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
console.error('[SDK Pixels] Initialization error:', error);
|
|
348
|
+
}
|
|
349
|
+
pixelsInitialized = true;
|
|
350
|
+
})();
|
|
351
|
+
const track = (eventName, parameters = {}, options = {}) => {
|
|
352
|
+
if (!pixels || !pixelsInitialized)
|
|
353
|
+
return;
|
|
354
|
+
if (!shouldTrackEvent(eventName, parameters, options.eventId))
|
|
355
|
+
return;
|
|
356
|
+
try {
|
|
357
|
+
const events = resolvePixelEvents(pixels, eventName, parameters);
|
|
358
|
+
// Deduplicate by provider: Meta/Snapchat/Pinterest SDKs broadcast
|
|
359
|
+
// to all registered pixel IDs internally, so we only need to fire once per
|
|
360
|
+
// provider. GTM and TikTok need per-pixel firing (GTM for Google Ads
|
|
361
|
+
// send_to targeting, TikTok because ttq.instance() targets a specific pixel).
|
|
362
|
+
const firedProviders = new Set();
|
|
363
|
+
for (const { provider, mapped, pixel } of events) {
|
|
364
|
+
if (provider === 'gtm' || provider === 'tiktok') {
|
|
365
|
+
fire(provider, mapped.name, mapped.params, pixel, options);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
if (!firedProviders.has(provider)) {
|
|
369
|
+
fire(provider, mapped.name, mapped.params, undefined, options);
|
|
370
|
+
firedProviders.add(provider);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error('[SDK Pixels] Tracking error:', error);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
// Auto page-view: fire once after init resolves.
|
|
380
|
+
initPromise.then(() => {
|
|
381
|
+
if (typeof window === 'undefined')
|
|
382
|
+
return;
|
|
383
|
+
track('PageView', { path: window.location.pathname });
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
initPromise,
|
|
387
|
+
get pixelsInitialized() { return pixelsInitialized; },
|
|
388
|
+
track,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function readPixelsFromStepConfig() {
|
|
392
|
+
if (typeof window === 'undefined')
|
|
393
|
+
return undefined;
|
|
394
|
+
const raw = window.__TGD_STEP_CONFIG__;
|
|
395
|
+
if (!raw)
|
|
396
|
+
return undefined;
|
|
397
|
+
try {
|
|
398
|
+
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
399
|
+
const pixels = parsed?.pixels;
|
|
400
|
+
return pixels && typeof pixels === 'object' ? pixels : undefined;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* One-shot bootstrap: reads `window.__TGD_STEP_CONFIG__.pixels`, creates a
|
|
408
|
+
* tracker, kicks off init, and stashes it on `window.__TGD_PIXEL_TRACKER__`.
|
|
409
|
+
*
|
|
410
|
+
* Idempotent — both the islands SDK and the full-SPA runtime may call this,
|
|
411
|
+
* but only the first call creates a tracker. Returns the active tracker
|
|
412
|
+
* (existing or newly created), or null if there's no window or no pixels.
|
|
413
|
+
*/
|
|
414
|
+
export function bootstrapPixelTrackerFromWindow() {
|
|
415
|
+
if (typeof window === 'undefined')
|
|
416
|
+
return null;
|
|
417
|
+
if (window.__TGD_PIXEL_TRACKER__)
|
|
418
|
+
return window.__TGD_PIXEL_TRACKER__;
|
|
419
|
+
const pixels = readPixelsFromStepConfig();
|
|
420
|
+
if (!pixels)
|
|
421
|
+
return null;
|
|
422
|
+
const tracker = createPixelTracker(pixels);
|
|
423
|
+
window.__TGD_PIXEL_TRACKER__ = tracker;
|
|
424
|
+
return tracker;
|
|
425
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { OrderAddress } from '../types';
|
|
6
6
|
import { ApiClient } from './apiClient';
|
|
7
|
+
import { EventBus } from '../utils/eventBus';
|
|
7
8
|
export type PaymentMethodName = 'card' | 'paypal' | 'klarna' | 'apple_pay' | 'google_pay' | 'link' | 'bridge' | 'whop' | 'zelle' | 'hipay' | 'crypto' | 'convesiopay' | 'oceanpayment' | 'afterpay' | 'ideal' | 'bancontact' | 'giropay' | 'sepa_debit' | (string & {});
|
|
8
9
|
export interface CheckoutLineItem {
|
|
9
10
|
externalProductId?: string | null;
|
|
@@ -86,6 +87,43 @@ export interface Upsell {
|
|
|
86
87
|
triggers: UpsellTrigger[];
|
|
87
88
|
orderBumpOffers: CheckoutOrderBumpOffer[];
|
|
88
89
|
}
|
|
90
|
+
export interface CheckoutOfferLineItem {
|
|
91
|
+
id: string;
|
|
92
|
+
productId: string;
|
|
93
|
+
variantId: string;
|
|
94
|
+
priceId: string;
|
|
95
|
+
quantity: number;
|
|
96
|
+
variant?: {
|
|
97
|
+
id: string;
|
|
98
|
+
name?: string | null;
|
|
99
|
+
imageUrl?: string | null;
|
|
100
|
+
} | null;
|
|
101
|
+
product?: {
|
|
102
|
+
id: string;
|
|
103
|
+
name?: string | null;
|
|
104
|
+
} | null;
|
|
105
|
+
price?: {
|
|
106
|
+
id: string;
|
|
107
|
+
currencyOptions?: Record<string, {
|
|
108
|
+
amount: number;
|
|
109
|
+
}> | null;
|
|
110
|
+
} | null;
|
|
111
|
+
}
|
|
112
|
+
/** Tiered checkout offer (offers row, type='checkout_offer'), nested into the
|
|
113
|
+
* checkout session response alongside store.upsells so the same fetch path
|
|
114
|
+
* delivers both. The CheckoutOfferSelector chunk consumes these. */
|
|
115
|
+
export interface CheckoutOfferRecord {
|
|
116
|
+
id: string;
|
|
117
|
+
type: string;
|
|
118
|
+
titleTrans: Record<string, string>;
|
|
119
|
+
summaries?: Array<{
|
|
120
|
+
currency?: string;
|
|
121
|
+
totalAmount?: number;
|
|
122
|
+
totalAdjustedAmount?: number;
|
|
123
|
+
[key: string]: unknown;
|
|
124
|
+
}> | null;
|
|
125
|
+
offerLineItems: CheckoutOfferLineItem[];
|
|
126
|
+
}
|
|
89
127
|
export interface Store {
|
|
90
128
|
id: string;
|
|
91
129
|
accountId: string;
|
|
@@ -93,6 +131,7 @@ export interface Store {
|
|
|
93
131
|
presentmentCurrencies: string[];
|
|
94
132
|
chargeCurrencies: string[];
|
|
95
133
|
upsells: Upsell[];
|
|
134
|
+
offers?: CheckoutOfferRecord[];
|
|
96
135
|
emailDomains: string[];
|
|
97
136
|
integrations: unknown[];
|
|
98
137
|
}
|
|
@@ -302,7 +341,8 @@ export interface CheckoutData {
|
|
|
302
341
|
}
|
|
303
342
|
export declare class CheckoutResource {
|
|
304
343
|
private apiClient;
|
|
305
|
-
|
|
344
|
+
private bus?;
|
|
345
|
+
constructor(apiClient: ApiClient, bus?: EventBus | undefined);
|
|
306
346
|
/**
|
|
307
347
|
* Initialize a new checkout session (sync mode)
|
|
308
348
|
* Response time: 2-5 seconds (full processing)
|
|
@@ -505,6 +545,10 @@ export declare class CheckoutResource {
|
|
|
505
545
|
shippingAddress: Partial<OrderAddress>;
|
|
506
546
|
billingAddress?: Partial<OrderAddress>;
|
|
507
547
|
differentBillingAddress?: boolean;
|
|
548
|
+
cartCustomAttributes?: Array<{
|
|
549
|
+
name: string;
|
|
550
|
+
value: string;
|
|
551
|
+
}>;
|
|
508
552
|
}): Promise<{
|
|
509
553
|
success: boolean;
|
|
510
554
|
errors?: Record<string, {
|