@tagadapay/plugin-sdk 4.0.0 → 4.0.4

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.
Files changed (65) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +499 -499
  3. package/dist/external-tracker.js +156 -2
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/providers/TagadaProvider.js +5 -5
  7. package/dist/tagada-react-sdk-minimal.min.js +2 -2
  8. package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
  9. package/dist/tagada-react-sdk.js +707 -253
  10. package/dist/tagada-react-sdk.min.js +2 -2
  11. package/dist/tagada-react-sdk.min.js.map +4 -4
  12. package/dist/tagada-sdk.js +2922 -102
  13. package/dist/tagada-sdk.min.js +2 -2
  14. package/dist/tagada-sdk.min.js.map +4 -4
  15. package/dist/v2/core/funnelClient.d.ts +40 -0
  16. package/dist/v2/core/funnelClient.js +30 -0
  17. package/dist/v2/core/pixelTracker.d.ts +51 -0
  18. package/dist/v2/core/pixelTracker.js +425 -0
  19. package/dist/v2/core/resources/checkout.d.ts +45 -1
  20. package/dist/v2/core/resources/checkout.js +13 -3
  21. package/dist/v2/core/resources/offers.d.ts +3 -3
  22. package/dist/v2/core/resources/offers.js +11 -3
  23. package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
  24. package/dist/v2/core/resources/promotionEvents.js +2 -0
  25. package/dist/v2/core/resources/promotions.d.ts +6 -1
  26. package/dist/v2/core/resources/promotions.js +6 -1
  27. package/dist/v2/core/resources/shippingRates.d.ts +18 -0
  28. package/dist/v2/core/resources/shippingRates.js +18 -0
  29. package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
  30. package/dist/v2/core/utils/clickIdResolver.js +169 -0
  31. package/dist/v2/core/utils/index.d.ts +2 -0
  32. package/dist/v2/core/utils/index.js +4 -0
  33. package/dist/v2/core/utils/metaEventId.d.ts +14 -0
  34. package/dist/v2/core/utils/metaEventId.js +16 -0
  35. package/dist/v2/core/utils/previewModeIndicator.js +101 -101
  36. package/dist/v2/index.d.ts +7 -0
  37. package/dist/v2/index.js +10 -0
  38. package/dist/v2/react/components/ApplePayButton.js +50 -0
  39. package/dist/v2/react/components/FunnelScriptInjector.js +9 -9
  40. package/dist/v2/react/components/GooglePayButton.js +39 -1
  41. package/dist/v2/react/components/StripeExpressButton.js +54 -2
  42. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +11 -11
  43. package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
  44. package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
  45. package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
  46. package/dist/v2/react/hooks/useFunnel.js +2 -1
  47. package/dist/v2/react/hooks/useOfferQuery.d.ts +11 -0
  48. package/dist/v2/react/hooks/useOfferQuery.js +11 -0
  49. package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
  50. package/dist/v2/react/hooks/usePixelTracking.js +32 -374
  51. package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
  52. package/dist/v2/react/hooks/usePreviewOffer.js +4 -2
  53. package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
  54. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
  55. package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
  56. package/dist/v2/react/hooks/useStepConfig.js +5 -1
  57. package/dist/v2/react/index.d.ts +5 -0
  58. package/dist/v2/react/index.js +9 -0
  59. package/dist/v2/react/providers/TagadaProvider.js +18 -5
  60. package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
  61. package/dist/v2/standalone/index.d.ts +3 -0
  62. package/dist/v2/standalone/index.js +23 -0
  63. package/dist/v2/standalone/payment-service.d.ts +54 -1
  64. package/dist/v2/standalone/payment-service.js +228 -61
  65. 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
- constructor(apiClient: ApiClient);
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, {