@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.
- package/dist/external-tracker.js +160 -6
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +2 -2
- package/dist/react/config/payment.js +5 -5
- package/dist/react/hooks/usePayment.d.ts +7 -0
- package/dist/react/hooks/usePayment.js +1 -0
- 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 +2220 -1428
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +3784 -128
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/config/environment.d.ts +3 -3
- package/dist/v2/core/config/environment.js +7 -7
- package/dist/v2/core/funnelClient.d.ts +42 -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/funnel.d.ts +1 -1
- package/dist/v2/core/resources/geo.d.ts +50 -0
- package/dist/v2/core/resources/geo.js +35 -0
- package/dist/v2/core/resources/offers.d.ts +1 -1
- package/dist/v2/core/resources/offers.js +3 -1
- package/dist/v2/core/resources/payments.d.ts +19 -1
- package/dist/v2/core/resources/payments.js +8 -0
- 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/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 +158 -10
- package/dist/v2/react/components/GooglePayButton.js +39 -1
- package/dist/v2/react/components/StripeExpressButton.d.ts +8 -0
- package/dist/v2/react/components/StripeExpressButton.js +76 -3
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.d.ts +15 -0
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +166 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +12 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +1 -0
- 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/useISOData.js +25 -7
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +1 -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/ExpressPaymentMethodsProvider.js +12 -4
- package/dist/v2/react/providers/TagadaProvider.js +13 -0
- package/dist/v2/standalone/apple-pay-service.d.ts +12 -0
- package/dist/v2/standalone/apple-pay-service.js +12 -0
- package/dist/v2/standalone/external-tracker.d.ts +1 -1
- package/dist/v2/standalone/google-pay-service.d.ts +9 -0
- package/dist/v2/standalone/google-pay-service.js +9 -0
- package/dist/v2/standalone/index.d.ts +11 -1
- package/dist/v2/standalone/index.js +30 -0
- package/dist/v2/standalone/payment-service.d.ts +72 -6
- package/dist/v2/standalone/payment-service.js +285 -65
- package/package.json +2 -1
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* usePixelTracking Hook & Provider
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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,
|
|
11
|
-
import {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
25
|
+
if (!pixels)
|
|
51
26
|
return;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
27
|
-
const [
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
}
|