@tagadapay/plugin-sdk 3.1.25 → 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.
Files changed (81) hide show
  1. package/dist/external-tracker.js +160 -6
  2. package/dist/external-tracker.min.js +2 -2
  3. package/dist/external-tracker.min.js.map +4 -4
  4. package/dist/react/config/payment.d.ts +2 -2
  5. package/dist/react/config/payment.js +5 -5
  6. package/dist/react/hooks/usePayment.d.ts +7 -0
  7. package/dist/react/hooks/usePayment.js +1 -0
  8. package/dist/tagada-react-sdk-minimal.min.js +2 -2
  9. package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
  10. package/dist/tagada-react-sdk.js +2220 -1428
  11. package/dist/tagada-react-sdk.min.js +2 -2
  12. package/dist/tagada-react-sdk.min.js.map +4 -4
  13. package/dist/tagada-sdk.js +3784 -128
  14. package/dist/tagada-sdk.min.js +2 -2
  15. package/dist/tagada-sdk.min.js.map +4 -4
  16. package/dist/v2/core/config/environment.d.ts +3 -3
  17. package/dist/v2/core/config/environment.js +7 -7
  18. package/dist/v2/core/funnelClient.d.ts +42 -0
  19. package/dist/v2/core/funnelClient.js +30 -0
  20. package/dist/v2/core/pixelTracker.d.ts +51 -0
  21. package/dist/v2/core/pixelTracker.js +425 -0
  22. package/dist/v2/core/resources/checkout.d.ts +45 -1
  23. package/dist/v2/core/resources/checkout.js +13 -3
  24. package/dist/v2/core/resources/funnel.d.ts +1 -1
  25. package/dist/v2/core/resources/geo.d.ts +50 -0
  26. package/dist/v2/core/resources/geo.js +35 -0
  27. package/dist/v2/core/resources/offers.d.ts +1 -1
  28. package/dist/v2/core/resources/offers.js +3 -1
  29. package/dist/v2/core/resources/payments.d.ts +19 -1
  30. package/dist/v2/core/resources/payments.js +8 -0
  31. package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
  32. package/dist/v2/core/resources/promotionEvents.js +2 -0
  33. package/dist/v2/core/resources/promotions.d.ts +6 -1
  34. package/dist/v2/core/resources/promotions.js +6 -1
  35. package/dist/v2/core/resources/shippingRates.d.ts +18 -0
  36. package/dist/v2/core/resources/shippingRates.js +18 -0
  37. package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
  38. package/dist/v2/core/utils/clickIdResolver.js +169 -0
  39. package/dist/v2/core/utils/index.d.ts +2 -0
  40. package/dist/v2/core/utils/index.js +4 -0
  41. package/dist/v2/core/utils/metaEventId.d.ts +14 -0
  42. package/dist/v2/core/utils/metaEventId.js +16 -0
  43. package/dist/v2/index.d.ts +7 -0
  44. package/dist/v2/index.js +10 -0
  45. package/dist/v2/react/components/ApplePayButton.js +50 -0
  46. package/dist/v2/react/components/FunnelScriptInjector.js +158 -10
  47. package/dist/v2/react/components/GooglePayButton.js +39 -1
  48. package/dist/v2/react/components/StripeExpressButton.d.ts +8 -0
  49. package/dist/v2/react/components/StripeExpressButton.js +76 -3
  50. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.d.ts +15 -0
  51. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +166 -0
  52. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +12 -0
  53. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +1 -0
  54. package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
  55. package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
  56. package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
  57. package/dist/v2/react/hooks/useFunnel.js +2 -1
  58. package/dist/v2/react/hooks/useISOData.js +25 -7
  59. package/dist/v2/react/hooks/usePaymentPolling.d.ts +1 -1
  60. package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
  61. package/dist/v2/react/hooks/usePixelTracking.js +32 -374
  62. package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
  63. package/dist/v2/react/hooks/usePreviewOffer.js +8 -2
  64. package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
  65. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
  66. package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
  67. package/dist/v2/react/hooks/useStepConfig.js +5 -1
  68. package/dist/v2/react/index.d.ts +4 -0
  69. package/dist/v2/react/index.js +8 -0
  70. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +12 -4
  71. package/dist/v2/react/providers/TagadaProvider.js +13 -0
  72. package/dist/v2/standalone/apple-pay-service.d.ts +12 -0
  73. package/dist/v2/standalone/apple-pay-service.js +12 -0
  74. package/dist/v2/standalone/external-tracker.d.ts +1 -1
  75. package/dist/v2/standalone/google-pay-service.d.ts +9 -0
  76. package/dist/v2/standalone/google-pay-service.js +9 -0
  77. package/dist/v2/standalone/index.d.ts +11 -1
  78. package/dist/v2/standalone/index.js +30 -0
  79. package/dist/v2/standalone/payment-service.d.ts +72 -6
  80. package/dist/v2/standalone/payment-service.js +285 -65
  81. package/package.json +2 -1
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * usePixelTracking Hook & Provider
3
3
  *
4
- * SDK-level pixel tracking based on runtime stepConfig.pixels injected
5
- * by the CRM. Uses core/pixelMapping for event mapping and gating,
6
- * keeping browser-specific init/fire logic here.
4
+ * Thin React adapter over `core/pixelTracker`. The provider creates a tracker
5
+ * for the current `stepConfig.pixels`, exposes `{ track, pixelsInitialized }`
6
+ * via context, and disposes/recreates the tracker when pixels change.
7
+ *
8
+ * Init + fire logic lives in `core/pixelTracker` so non-React entries (Studio
9
+ * islands, full-SPA runtime) can fire pixels without mounting a provider.
7
10
  */
8
11
  import React from 'react';
9
- import { type StandardPixelEvent } from '../../core/pixelMapping';
12
+ import { type TrackOptions } from '../../core/pixelTracker';
13
+ import type { StandardPixelEvent } from '../../core/pixelMapping';
10
14
  export type { StandardPixelEvent } from '../../core/pixelMapping';
15
+ export type { TrackOptions } from '../../core/pixelTracker';
11
16
  export interface PixelTrackingContextValue {
12
- track: (eventName: StandardPixelEvent, parameters?: Record<string, unknown>) => void;
17
+ track: (eventName: StandardPixelEvent, parameters?: Record<string, unknown>, options?: TrackOptions) => void;
13
18
  pixelsInitialized: boolean;
14
19
  }
15
20
  export declare function PixelTrackingProvider({ children }: {
@@ -3,114 +3,46 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
4
  * usePixelTracking Hook & Provider
5
5
  *
6
- * SDK-level pixel tracking based on runtime stepConfig.pixels injected
7
- * by the CRM. Uses core/pixelMapping for event mapping and gating,
8
- * keeping browser-specific init/fire logic here.
6
+ * Thin React adapter over `core/pixelTracker`. The provider creates a tracker
7
+ * for the current `stepConfig.pixels`, exposes `{ track, pixelsInitialized }`
8
+ * via context, and disposes/recreates the tracker when pixels change.
9
+ *
10
+ * Init + fire logic lives in `core/pixelTracker` so non-React entries (Studio
11
+ * islands, full-SPA runtime) can fire pixels without mounting a provider.
9
12
  */
10
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
11
- import { resolvePixelEvents, } from '../../core/pixelMapping';
13
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, } from 'react';
14
+ import { createPixelTracker, } from '../../core/pixelTracker';
12
15
  import { useStepConfig } from './useStepConfig';
13
16
  const PixelTrackingContext = createContext(null);
14
- // ---------------------------------------------------------------------------
15
- // Duplicate guard (time-window based)
16
- // ---------------------------------------------------------------------------
17
- function createDuplicateGuard(windowMs) {
18
- const lastEvents = new Map();
19
- return (eventName, parameters) => {
20
- try {
21
- const key = JSON.stringify({ eventName, parameters });
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
- const shouldTrackEvent = createDuplicateGuard(5000);
35
- // ---------------------------------------------------------------------------
36
- // Provider
37
- // ---------------------------------------------------------------------------
38
17
  export function PixelTrackingProvider({ children }) {
39
18
  const { pixels } = useStepConfig();
19
+ const trackerRef = useRef(null);
40
20
  const [pixelsInitialized, setPixelsInitialized] = useState(false);
41
- const isMountedRef = useRef(true);
42
- useEffect(() => {
43
- isMountedRef.current = true;
44
- return () => { isMountedRef.current = false; };
45
- }, []);
46
- // ---- Initialize pixel scripts once ----
47
- // Wait for all external scripts to actually load before marking initialized,
48
- // so that pixel helpers (TikTok, etc.) can intercept events properly.
21
+ // Recreate the tracker when `pixels` changes. The underlying init helpers
22
+ // are idempotent — base scripts and per-pixel `init` calls de-duplicate
23
+ // themselves so a rebuild won't double-load scripts.
49
24
  useEffect(() => {
50
- if (!pixels || pixelsInitialized || !isMountedRef.current)
25
+ if (!pixels)
51
26
  return;
52
- const loadPromises = [];
53
- try {
54
- pixels.facebook?.forEach((px) => { if (px.enabled && px.pixelId)
55
- loadPromises.push(initMetaPixel(px.pixelId)); });
56
- pixels.tiktok?.forEach((px) => { if (px.enabled && px.pixelId)
57
- loadPromises.push(initTikTokPixel(px.pixelId)); });
58
- pixels.snapchat?.forEach((px) => { if (px.enabled && px.pixelId)
59
- loadPromises.push(initSnapchatPixel(px.pixelId)); });
60
- pixels.pinterest?.forEach((px) => { if (px.enabled && px.pixelId)
61
- loadPromises.push(initPinterestPixel(px.pixelId)); });
62
- pixels.gtm?.forEach((px) => { if (px.enabled && px.containerId)
63
- loadPromises.push(initGTM(px.containerId)); });
64
- Promise.all(loadPromises).then(() => {
65
- if (isMountedRef.current)
66
- setPixelsInitialized(true);
67
- });
68
- }
69
- catch (error) {
70
- console.error('[SDK Pixels] Initialization error:', error);
71
- }
72
- }, [pixels, pixelsInitialized]);
73
- // ---- Track function ----
74
- const track = useCallback((eventName, parameters = {}) => {
75
- if (!pixels || !pixelsInitialized || !isMountedRef.current)
76
- return;
77
- if (!shouldTrackEvent(eventName, parameters))
78
- return;
79
- try {
80
- const events = resolvePixelEvents(pixels, eventName, parameters);
81
- // Deduplicate by provider: Meta/Snapchat/Pinterest SDKs broadcast
82
- // to all registered pixel IDs internally, so we only need to fire once per
83
- // provider. GTM and TikTok need per-pixel firing (GTM for Google Ads
84
- // send_to targeting, TikTok because ttq.instance() targets a specific pixel).
85
- const firedProviders = new Set();
86
- for (const { provider, mapped, pixel } of events) {
87
- if (provider === 'gtm' || provider === 'tiktok') {
88
- fire(provider, mapped.name, mapped.params, pixel);
89
- }
90
- else {
91
- if (!firedProviders.has(provider)) {
92
- fire(provider, mapped.name, mapped.params);
93
- firedProviders.add(provider);
94
- }
95
- }
96
- }
97
- }
98
- catch (error) {
99
- console.error('[SDK Pixels] Tracking error:', error);
100
- }
101
- }, [pixels, pixelsInitialized]);
102
- // ---- Auto page-view ----
103
- useEffect(() => {
104
- if (!pixelsInitialized || !isMountedRef.current)
105
- return;
106
- const id = setTimeout(() => {
107
- if (isMountedRef.current) {
108
- track('PageView', { path: typeof window !== 'undefined' ? window.location.pathname : '' });
109
- }
110
- }, 0);
111
- return () => clearTimeout(id);
112
- }, [pixelsInitialized, track]);
113
- const value = useMemo(() => ({ track, pixelsInitialized }), [track, pixelsInitialized]);
27
+ const tracker = createPixelTracker(pixels);
28
+ trackerRef.current = tracker;
29
+ setPixelsInitialized(false);
30
+ let cancelled = false;
31
+ tracker.initPromise.then(() => {
32
+ if (!cancelled)
33
+ setPixelsInitialized(true);
34
+ });
35
+ return () => {
36
+ cancelled = true;
37
+ trackerRef.current = null;
38
+ };
39
+ }, [pixels]);
40
+ const value = useMemo(() => ({
41
+ track: (eventName, parameters, options) => {
42
+ trackerRef.current?.track(eventName, parameters, options);
43
+ },
44
+ pixelsInitialized,
45
+ }), [pixelsInitialized]);
114
46
  return _jsx(PixelTrackingContext.Provider, { value: value, children: children });
115
47
  }
116
48
  export function usePixelTracking() {
@@ -119,277 +51,3 @@ export function usePixelTracking() {
119
51
  throw new Error('usePixelTracking must be used within a PixelTrackingProvider');
120
52
  return context;
121
53
  }
122
- // ---------------------------------------------------------------------------
123
- // Browser-specific: fire an event to the correct global pixel function
124
- // ---------------------------------------------------------------------------
125
- /* eslint-disable @typescript-eslint/no-explicit-any */
126
- function fire(provider, name, params, pixel) {
127
- if (typeof window === 'undefined')
128
- return;
129
- const w = window;
130
- switch (provider) {
131
- case 'facebook':
132
- w.fbq?.('track', name, params);
133
- break;
134
- case 'tiktok': {
135
- // Use ttq.instance(pixelId) to target each pixel individually,
136
- // since the global ttq.track() only fires for the sdkid pixel.
137
- const pixelId = pixel && 'pixelId' in pixel ? pixel.pixelId : null;
138
- const target = pixelId && w.ttq?.instance ? w.ttq.instance(pixelId) : w.ttq;
139
- if (name === 'Pageview') {
140
- target?.page?.();
141
- }
142
- else {
143
- target?.track?.(name, params);
144
- }
145
- break;
146
- }
147
- case 'snapchat':
148
- w.snaptr?.('track', name, params);
149
- break;
150
- case 'pinterest':
151
- // Pinterest handles page views via pintrk('page'), not pintrk('track', 'pagevisit')
152
- if (name === 'pagevisit') {
153
- w.pintrk?.('page');
154
- }
155
- else {
156
- w.pintrk?.('track', name, params);
157
- }
158
- break;
159
- case 'gtm':
160
- fireGTM(name, params);
161
- break;
162
- }
163
- }
164
- function fireGTM(event, params) {
165
- try {
166
- const w = window;
167
- if (w.gtag) {
168
- w.gtag('event', event, params);
169
- }
170
- else if (w.dataLayer) {
171
- w.dataLayer.push({ event, ...params });
172
- }
173
- }
174
- catch (error) {
175
- console.error('[SDK GTM] Error:', error);
176
- }
177
- }
178
- /* eslint-enable @typescript-eslint/no-explicit-any */
179
- // ===========================================================================
180
- // Pixel initialization (browser-only)
181
- // All pixel globals are accessed via `win` typed as `any` to avoid
182
- // `declare global` augmentation issues across tsconfig scopes.
183
- // ===========================================================================
184
- /* eslint-disable @typescript-eslint/no-explicit-any */
185
- const win = (typeof window !== 'undefined' ? window : undefined);
186
- const SCRIPT_LOAD_TIMEOUT_MS = 5000;
187
- function waitForScriptLoad(script) {
188
- // If the script has already loaded, resolve immediately
189
- if (script.dataset.loaded)
190
- return Promise.resolve();
191
- return new Promise((resolve) => {
192
- const done = () => { script.dataset.loaded = '1'; clearTimeout(timer); resolve(); };
193
- const timer = setTimeout(done, SCRIPT_LOAD_TIMEOUT_MS);
194
- script.addEventListener('load', done, { once: true });
195
- script.addEventListener('error', done, { once: true });
196
- });
197
- }
198
- let _metaScriptEl = null;
199
- function initMetaPixel(pixelId) {
200
- if (!win)
201
- return Promise.resolve();
202
- // Initialize Meta base code once
203
- if (!win.fbq) {
204
- const n = function (...args) {
205
- if (n.callMethod)
206
- n.callMethod(...args);
207
- else
208
- n.queue.push(args);
209
- };
210
- n.queue = [];
211
- n.loaded = true;
212
- n.version = '2.0';
213
- win.fbq = n;
214
- if (!win._fbq)
215
- win._fbq = n;
216
- const t = document.createElement('script');
217
- t.async = true;
218
- t.src = 'https://connect.facebook.net/en_US/fbevents.js';
219
- const s = document.getElementsByTagName('script')[0];
220
- s?.parentNode?.insertBefore(t, s);
221
- _metaScriptEl = t;
222
- }
223
- // Register each pixel ID (fbq supports multiple pixels via multiple init calls)
224
- win.fbq('init', pixelId);
225
- return _metaScriptEl ? waitForScriptLoad(_metaScriptEl) : Promise.resolve();
226
- }
227
- let _tiktokBaseInitialized = false;
228
- function initTikTokPixel(pixelId) {
229
- if (!win)
230
- return Promise.resolve();
231
- // Initialize TikTok base code once
232
- if (!_tiktokBaseInitialized) {
233
- _tiktokBaseInitialized = true;
234
- win.TiktokAnalyticsObject = 'ttq';
235
- const ttq = (win.ttq = win.ttq || []);
236
- ttq.methods = [
237
- 'page', 'track', 'identify', 'instances', 'debug', 'on', 'off',
238
- 'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie',
239
- 'holdConsent', 'revokeConsent', 'grantConsent',
240
- ];
241
- ttq.setAndDefer = function (t, e) {
242
- t[e] = function (...args) { t.push([e, ...args]); };
243
- };
244
- for (const method of ttq.methods) {
245
- ttq.setAndDefer(ttq, method);
246
- }
247
- ttq.instance = function (t) {
248
- const e = ttq._i[t] || [];
249
- for (const method of ttq.methods) {
250
- ttq.setAndDefer(e, method);
251
- }
252
- return e;
253
- };
254
- ttq.load = function (e, n) {
255
- const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
256
- ttq._i = ttq._i || {};
257
- ttq._i[e] = [];
258
- ttq._i[e]._u = r;
259
- ttq._t = ttq._t || {};
260
- ttq._t[e] = +new Date();
261
- ttq._o = ttq._o || {};
262
- ttq._o[e] = n || {};
263
- const s = document.createElement('script');
264
- s.type = 'text/javascript';
265
- s.async = true;
266
- s.src = r + '?sdkid=' + e + '&lib=ttq';
267
- const p = document.getElementsByTagName('script')[0];
268
- p?.parentNode?.insertBefore(s, p);
269
- };
270
- }
271
- // Skip if this specific pixel ID is already registered
272
- if (win.ttq._i?.[pixelId])
273
- return Promise.resolve();
274
- // Register pixel and load its script
275
- win.ttq.load(pixelId);
276
- // Find the script we just created and wait for it
277
- const scripts = document.querySelectorAll('script[src*="analytics.tiktok.com/i18n/pixel/events.js"]');
278
- const lastScript = scripts[scripts.length - 1];
279
- return lastScript ? waitForScriptLoad(lastScript) : Promise.resolve();
280
- }
281
- let _snapchatScriptEl = null;
282
- function initSnapchatPixel(pixelId) {
283
- if (!win)
284
- return Promise.resolve();
285
- // Initialize Snapchat base code once
286
- if (!win.snaptr) {
287
- const a = function (...args) {
288
- if (a.handleRequest)
289
- a.handleRequest(...args);
290
- else
291
- a.queue.push(args);
292
- };
293
- a.queue = [];
294
- win.snaptr = a;
295
- const r = document.createElement('script');
296
- r.async = true;
297
- r.src = 'https://sc-static.net/scevent.min.js';
298
- const u = document.getElementsByTagName('script')[0];
299
- u?.parentNode?.insertBefore(r, u);
300
- _snapchatScriptEl = r;
301
- }
302
- // Register each pixel ID (snaptr supports multiple pixels via multiple init calls)
303
- win.snaptr('init', pixelId);
304
- return _snapchatScriptEl ? waitForScriptLoad(_snapchatScriptEl) : Promise.resolve();
305
- }
306
- let _pinterestScriptEl = null;
307
- function initPinterestPixel(pixelId) {
308
- if (!win)
309
- return Promise.resolve();
310
- // Initialize Pinterest base code once
311
- if (!win.pintrk) {
312
- const a = function (...args) { a.queue.push(args); };
313
- a.queue = [];
314
- win.pintrk = a;
315
- const s = document.createElement('script');
316
- s.async = true;
317
- s.src = 'https://s.pinimg.com/ct/core.js';
318
- const u = document.getElementsByTagName('script')[0];
319
- u?.parentNode?.insertBefore(s, u);
320
- _pinterestScriptEl = s;
321
- }
322
- // Register each pixel ID (pintrk supports multiple pixels via multiple load calls)
323
- win.pintrk('load', pixelId);
324
- // Note: pintrk('page') is NOT called here — the Provider's auto page-view
325
- // effect handles it via fire(). Calling both would double-count page views.
326
- return _pinterestScriptEl ? waitForScriptLoad(_pinterestScriptEl) : Promise.resolve();
327
- }
328
- function initGTM(containerId) {
329
- if (!win || !containerId)
330
- return Promise.resolve();
331
- const isGtmContainer = containerId.startsWith('GTM-');
332
- const scriptUrlPart = isGtmContainer
333
- ? `googletagmanager.com/gtm.js?id=${containerId}`
334
- : `googletagmanager.com/gtag/js?id=${containerId}`;
335
- if (document.querySelector(`script[src*="${scriptUrlPart}"]`))
336
- return Promise.resolve();
337
- win.dataLayer = win.dataLayer || [];
338
- // Push referrer domain context into dataLayer before GTM loads, so tags
339
- // inside the container can use it for cross-domain tracking configuration.
340
- try {
341
- const ref = document.referrer && new URL(document.referrer).hostname;
342
- if (ref && ref !== window.location.hostname) {
343
- win.dataLayer.push({ tagada_referrer_domain: ref });
344
- }
345
- }
346
- catch { /* ignore invalid referrer */ }
347
- if (isGtmContainer) {
348
- win.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
349
- const f = document.getElementsByTagName('script')[0];
350
- const j = document.createElement('script');
351
- j.async = true;
352
- j.src = 'https://www.googletagmanager.com/gtm.js?id=' + containerId;
353
- if (f?.parentNode)
354
- f.parentNode.insertBefore(j, f);
355
- else
356
- (document.head || document.getElementsByTagName('head')[0])?.appendChild(j);
357
- const noscript = document.createElement('noscript');
358
- const iframe = document.createElement('iframe');
359
- iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
360
- iframe.height = '0';
361
- iframe.width = '0';
362
- iframe.style.display = 'none';
363
- iframe.style.visibility = 'hidden';
364
- noscript.appendChild(iframe);
365
- (document.body || document.getElementsByTagName('body')[0])?.insertBefore(noscript, document.body?.firstChild ?? null);
366
- return waitForScriptLoad(j);
367
- }
368
- else {
369
- if (!win.gtag) {
370
- win.gtag = function (..._args) { win.dataLayer.push(arguments); };
371
- }
372
- win.gtag('js', new Date());
373
- // Enable cross-domain tracking: accept incoming _gl parameter from referring
374
- // domains (e.g. Shopify store → TagadaPay checkout) so Google preserves the
375
- // client-id/session across the redirect.
376
- const linkerConfig = { accept_incoming: true };
377
- try {
378
- const ref = document.referrer && new URL(document.referrer).hostname;
379
- if (ref && ref !== window.location.hostname) {
380
- linkerConfig.domains = [ref, window.location.hostname];
381
- }
382
- }
383
- catch { /* ignore invalid referrer */ }
384
- win.gtag('config', containerId, { linker: linkerConfig });
385
- const script = document.createElement('script');
386
- script.async = true;
387
- script.src = 'https://www.googletagmanager.com/gtag/js?id=' + containerId;
388
- const firstScript = document.getElementsByTagName('script')[0];
389
- if (firstScript?.parentNode)
390
- firstScript.parentNode.insertBefore(script, firstScript);
391
- else
392
- (document.head || document.getElementsByTagName('head')[0])?.appendChild(script);
393
- return waitForScriptLoad(script);
394
- }
395
- }
@@ -72,7 +72,9 @@ export interface UsePreviewOfferResult {
72
72
  unitAmount: number;
73
73
  currency: string;
74
74
  }>;
75
- pay: (mainOrderId?: string) => Promise<{
75
+ pay: (mainOrderId?: string, options?: {
76
+ initiatedBy?: 'merchant' | 'customer';
77
+ }) => Promise<{
76
78
  checkoutUrl: string;
77
79
  }>;
78
80
  toCheckout: (mainOrderId?: string) => Promise<{
@@ -10,6 +10,7 @@
10
10
  import { useQuery } from '@tanstack/react-query';
11
11
  import { useCallback, useEffect, useMemo, useState } from 'react';
12
12
  import { OffersResource } from '../../core/resources/offers';
13
+ import { getAssignedPaymentInitiator } from '../../core/funnelClient';
13
14
  import { getGlobalApiClient } from './useApiQuery';
14
15
  export function usePreviewOffer(options) {
15
16
  const { offerId, currency: requestedCurrency = '', initialSelections = {} } = options;
@@ -228,13 +229,18 @@ export function usePreviewOffer(options) {
228
229
  });
229
230
  }, [summary, effectiveCurrency]);
230
231
  // Pay for the offer with current selections
231
- const pay = useCallback(async (mainOrderId) => {
232
+ const pay = useCallback(async (mainOrderId, payOptions) => {
232
233
  if (isPaying) {
233
234
  throw new Error('Payment already in progress');
234
235
  }
235
236
  setIsPaying(true);
236
237
  try {
237
- const result = await offersResource.payPreviewedOffer(offerId, effectiveCurrency, lineItemsForPreview, typeof window !== 'undefined' ? window.location.href : undefined, mainOrderId);
238
+ // Auto-pickup: stepConfig.paymentInitiator from the funnel client is
239
+ // applied when the caller does not pass an explicit override. This
240
+ // way the MIT/CIT toggle in the CRM step config takes effect for
241
+ // every consumer of usePreviewOffer without per-template wiring.
242
+ const initiatedBy = payOptions?.initiatedBy ?? getAssignedPaymentInitiator();
243
+ const result = await offersResource.payPreviewedOffer(offerId, effectiveCurrency, lineItemsForPreview, typeof window !== 'undefined' ? window.location.href : undefined, mainOrderId, initiatedBy);
238
244
  console.log('[usePreviewOffer] Payment initiated:', result);
239
245
  return {
240
246
  checkoutUrl: result.checkout.checkoutUrl,
@@ -6,19 +6,21 @@ import { useMemo } from 'react';
6
6
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
7
7
  import { PromotionsResource } from '../../core/resources/promotions';
8
8
  import { getGlobalApiClient } from './useApiQuery';
9
+ import { useTagadaContext } from '../providers/TagadaProvider';
9
10
  export function usePromotionsQuery(options = {}) {
10
11
  const { checkoutSessionId, enabled = true } = options;
11
12
  const queryClient = useQueryClient();
12
- const client = getGlobalApiClient();
13
+ const apiClient = getGlobalApiClient();
14
+ const { client } = useTagadaContext();
13
15
  // Create resource client
14
16
  const promotionsResource = useMemo(() => {
15
17
  try {
16
- return new PromotionsResource(client);
18
+ return new PromotionsResource(apiClient, client.bus);
17
19
  }
18
20
  catch (error) {
19
21
  throw new Error('Failed to initialize promotions resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
20
22
  }
21
- }, [client]);
23
+ }, [apiClient, client.bus]);
22
24
  // Main promotions query
23
25
  const { data: appliedPromotions = [], isLoading, error, refetch, } = useQuery({
24
26
  queryKey: ['promotions', checkoutSessionId],
@@ -40,6 +42,8 @@ export function usePromotionsQuery(options = {}) {
40
42
  if (checkoutSessionId) {
41
43
  void queryClient.invalidateQueries({ queryKey: ['promotions', checkoutSessionId] });
42
44
  void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutSessionId] });
45
+ void queryClient.invalidateQueries({ queryKey: ['shipping-rates', checkoutSessionId] });
46
+ void queryClient.invalidateQueries({ queryKey: ['shipping-rates-preview', checkoutSessionId] });
43
47
  }
44
48
  },
45
49
  });
@@ -56,6 +60,8 @@ export function usePromotionsQuery(options = {}) {
56
60
  if (checkoutSessionId) {
57
61
  void queryClient.invalidateQueries({ queryKey: ['promotions', checkoutSessionId] });
58
62
  void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutSessionId] });
63
+ void queryClient.invalidateQueries({ queryKey: ['shipping-rates', checkoutSessionId] });
64
+ void queryClient.invalidateQueries({ queryKey: ['shipping-rates-preview', checkoutSessionId] });
59
65
  }
60
66
  },
61
67
  });
@@ -23,9 +23,8 @@ export function useShippingRatesQuery(options = {}) {
23
23
  const effectiveSessionId = sessionId || checkout?.checkoutSession?.id;
24
24
  // Track if we've synced the initial selection from checkout
25
25
  const hasSyncedInitialSelectionRef = useRef(false);
26
- // Preview rates state
27
- const [previewedRates, setPreviewedRates] = useState();
28
- const [isPreviewLoading, setIsPreviewLoading] = useState(false);
26
+ // Preview rates params - triggers preview query when set
27
+ const [previewParams, setPreviewParams] = useState(null);
29
28
  // Main shipping rates query
30
29
  const { data: shippingRatesData, isLoading: isFetching, error: fetchError, refetch: refetchRates, } = useQuery({
31
30
  queryKey: ['shipping-rates', effectiveSessionId],
@@ -54,6 +53,23 @@ export function useShippingRatesQuery(options = {}) {
54
53
  });
55
54
  // Get sorted shipping rates from query data
56
55
  const shippingRates = shippingRatesData?.rates;
56
+ // Preview shipping rates query - invalidated by promotions mutations
57
+ const { data: previewedRates, isLoading: isPreviewLoading, } = useQuery({
58
+ queryKey: ['shipping-rates-preview', effectiveSessionId, previewParams?.countryCode, previewParams?.stateCode],
59
+ queryFn: async () => {
60
+ const response = await shippingRatesResource.previewShippingRates(effectiveSessionId, previewParams);
61
+ return [...response.rates].sort((a, b) => {
62
+ if (a.isFree && !b.isFree)
63
+ return -1;
64
+ if (!a.isFree && b.isFree)
65
+ return 1;
66
+ return (a.amount ?? 0) - (b.amount ?? 0);
67
+ });
68
+ },
69
+ enabled: !!effectiveSessionId && !!previewParams,
70
+ staleTime: 30000,
71
+ refetchOnWindowFocus: false,
72
+ });
57
73
  // Get selected rate from checkout data
58
74
  const checkoutSelectedRateId = checkout?.checkoutSession?.shippingRate?.id;
59
75
  // Set shipping rate mutation
@@ -137,31 +153,30 @@ export function useShippingRatesQuery(options = {}) {
137
153
  const previewRates = useCallback(async (countryCode, stateCode) => {
138
154
  if (!enabled || !effectiveSessionId)
139
155
  return [];
156
+ setPreviewParams({ countryCode, stateCode });
140
157
  try {
141
- setIsPreviewLoading(true);
142
- const response = await shippingRatesResource.previewShippingRates(effectiveSessionId, {
143
- countryCode,
144
- stateCode,
145
- });
146
- // Sort rates: free first, then by ascending amount
147
- const sortedRates = [...response.rates].sort((a, b) => {
148
- if (a.isFree && !b.isFree)
149
- return -1;
150
- if (!a.isFree && b.isFree)
151
- return 1;
152
- return (a.amount ?? 0) - (b.amount ?? 0);
158
+ return await queryClient.fetchQuery({
159
+ queryKey: ['shipping-rates-preview', effectiveSessionId, countryCode, stateCode],
160
+ queryFn: async () => {
161
+ const response = await shippingRatesResource.previewShippingRates(effectiveSessionId, {
162
+ countryCode,
163
+ stateCode,
164
+ });
165
+ return [...response.rates].sort((a, b) => {
166
+ if (a.isFree && !b.isFree)
167
+ return -1;
168
+ if (!a.isFree && b.isFree)
169
+ return 1;
170
+ return (a.amount ?? 0) - (b.amount ?? 0);
171
+ });
172
+ },
153
173
  });
154
- setPreviewedRates(sortedRates);
155
- return sortedRates;
156
174
  }
157
175
  catch (error) {
158
176
  console.error('[useShippingRatesQuery] Error previewing shipping rates:', error);
159
177
  return [];
160
178
  }
161
- finally {
162
- setIsPreviewLoading(false);
163
- }
164
- }, [enabled, effectiveSessionId, shippingRatesResource]);
179
+ }, [enabled, effectiveSessionId, queryClient, shippingRatesResource]);
165
180
  return {
166
181
  shippingRates,
167
182
  selectedRate,
@@ -63,6 +63,15 @@ export interface UseStepConfigResult {
63
63
  * undefined = inherit all store upsells, string[] = only these IDs.
64
64
  */
65
65
  upsellOfferIds: string[] | undefined;
66
+ /**
67
+ * Enabled in-step checkout-offer upsell IDs (tiered selector).
68
+ * undefined = inherit all store upsells of type='upsell'.
69
+ */
70
+ checkoutOfferIds: string[] | undefined;
71
+ /**
72
+ * Default checkout-offer upsell ID configured for this step (if any).
73
+ */
74
+ checkoutOfferDefaultId: string | undefined;
66
75
  }
67
76
  /**
68
77
  * Hook to access runtime step configuration injected via HTML
@@ -22,7 +22,7 @@
22
22
  * ```
23
23
  */
24
24
  import { useMemo } from 'react';
25
- import { getAssignedOrderBumpOfferIds, getAssignedPaymentFlowId, getAssignedPixels, getAssignedResources, getAssignedScripts, getAssignedStepConfig, getAssignedUpsellOfferIds, } from '../../core/funnelClient';
25
+ import { getAssignedCheckoutOfferDefault, getAssignedCheckoutOfferIds, getAssignedOrderBumpOfferIds, getAssignedPaymentFlowId, getAssignedPixels, getAssignedResources, getAssignedScripts, getAssignedStepConfig, getAssignedUpsellOfferIds, } from '../../core/funnelClient';
26
26
  /**
27
27
  * Hook to access runtime step configuration injected via HTML
28
28
  *
@@ -46,6 +46,8 @@ export function useStepConfig() {
46
46
  const paymentSetupConfig = useMemo(() => stepConfig?.paymentSetupConfig, [stepConfig]);
47
47
  const orderBumpOfferIds = useMemo(() => getAssignedOrderBumpOfferIds(), []);
48
48
  const upsellOfferIds = useMemo(() => getAssignedUpsellOfferIds(), []);
49
+ const checkoutOfferIds = useMemo(() => getAssignedCheckoutOfferIds(), []);
50
+ const checkoutOfferDefaultId = useMemo(() => getAssignedCheckoutOfferDefault(), []);
49
51
  return {
50
52
  stepConfig,
51
53
  paymentFlowId,
@@ -56,5 +58,7 @@ export function useStepConfig() {
56
58
  paymentSetupConfig,
57
59
  orderBumpOfferIds,
58
60
  upsellOfferIds,
61
+ checkoutOfferIds,
62
+ checkoutOfferDefaultId,
59
63
  };
60
64
  }