@tagadapay/plugin-sdk 3.1.22 → 3.1.25
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/build-cdn.js +274 -6
- package/dist/external-tracker.js +476 -6774
- package/dist/external-tracker.min.js +2 -25
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +14 -4
- package/dist/react/config/payment.js +47 -9
- package/dist/react/hooks/useCheckout.d.ts +3 -0
- package/dist/react/hooks/useCheckout.js +11 -3
- package/dist/react/hooks/usePluginConfig.js +9 -10
- package/dist/react/providers/TagadaProvider.js +1 -1
- package/dist/tagada-react-sdk-minimal.min.js +36 -0
- package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
- package/dist/tagada-react-sdk.js +37988 -0
- package/dist/tagada-react-sdk.min.js +78 -0
- package/dist/tagada-react-sdk.min.js.map +7 -0
- package/dist/tagada-sdk.js +7847 -6420
- package/dist/tagada-sdk.min.js +4 -22
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/cdn-react-minimal.d.ts +23 -0
- package/dist/v2/cdn-react-minimal.js +26 -0
- package/dist/v2/core/client.js +2 -1
- package/dist/v2/core/config/environment.js +2 -1
- package/dist/v2/core/funnelClient.d.ts +106 -10
- package/dist/v2/core/funnelClient.js +122 -28
- package/dist/v2/core/index.d.ts +0 -1
- package/dist/v2/core/index.js +0 -2
- package/dist/v2/core/isoData.d.ts +4 -4
- package/dist/v2/core/isoData.js +7 -7
- package/dist/v2/core/pixelMapping.js +64 -26
- package/dist/v2/core/resources/apiClient.d.ts +18 -14
- package/dist/v2/core/resources/apiClient.js +151 -109
- package/dist/v2/core/resources/checkout.d.ts +10 -0
- package/dist/v2/core/resources/checkout.js +6 -0
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
- package/dist/v2/core/resources/index.d.ts +1 -1
- package/dist/v2/core/resources/index.js +1 -1
- package/dist/v2/core/resources/offers.js +4 -4
- package/dist/v2/core/resources/payments.d.ts +8 -2
- package/dist/v2/core/resources/payments.js +1 -0
- package/dist/v2/core/resources/postPurchases.d.ts +17 -0
- package/dist/v2/core/resources/postPurchases.js +20 -0
- package/dist/v2/core/utils/currency.d.ts +3 -0
- package/dist/v2/core/utils/currency.js +40 -2
- package/dist/v2/core/utils/deviceInfo.d.ts +1 -10
- package/dist/v2/core/utils/deviceInfo.js +153 -76
- package/dist/v2/core/utils/order.d.ts +2 -0
- package/dist/v2/core/utils/pluginConfig.js +18 -22
- package/dist/v2/core/utils/previewMode.js +12 -0
- package/dist/v2/index.d.ts +4 -3
- package/dist/v2/index.js +4 -2
- package/dist/v2/react/components/ApplePayButton.js +39 -16
- package/dist/v2/react/components/FunnelScriptInjector.js +145 -77
- package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
- package/dist/v2/react/components/StripeExpressButton.js +170 -0
- package/dist/v2/react/components/WhopCheckout.js +7 -1
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +1 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +21 -3
- package/dist/v2/react/hooks/useApiQuery.d.ts +1 -1
- package/dist/v2/react/hooks/useApiQuery.js +1 -1
- package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +10 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +27 -15
- package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
- package/dist/v2/react/hooks/useFunnel.js +8 -4
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
- package/dist/v2/react/hooks/useISOData.d.ts +2 -5
- package/dist/v2/react/hooks/useISOData.js +25 -26
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +2 -2
- package/dist/v2/react/hooks/usePixelTracking.js +151 -70
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
- package/dist/v2/react/hooks/usePreviewOffer.js +1 -1
- package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
- package/dist/v2/react/hooks/useRemappableParams.js +23 -23
- package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
- package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
- package/dist/v2/react/hooks/useStepConfig.js +14 -7
- package/dist/v2/react/hooks/useTranslation.js +23 -8
- package/dist/v2/react/index.d.ts +8 -1
- package/dist/v2/react/index.js +3 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +8 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +106 -10
- package/dist/v2/react/providers/TagadaProvider.js +5 -5
- package/dist/v2/standalone/index.d.ts +21 -3
- package/dist/v2/standalone/index.js +25 -3
- package/dist/v2/standalone/payment-service.d.ts +134 -0
- package/dist/v2/standalone/payment-service.js +929 -0
- package/package.json +4 -2
|
@@ -44,22 +44,27 @@ export function PixelTrackingProvider({ children }) {
|
|
|
44
44
|
return () => { isMountedRef.current = false; };
|
|
45
45
|
}, []);
|
|
46
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.
|
|
47
49
|
useEffect(() => {
|
|
48
50
|
if (!pixels || pixelsInitialized || !isMountedRef.current)
|
|
49
51
|
return;
|
|
52
|
+
const loadPromises = [];
|
|
50
53
|
try {
|
|
51
54
|
pixels.facebook?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
52
|
-
initMetaPixel(px.pixelId); });
|
|
55
|
+
loadPromises.push(initMetaPixel(px.pixelId)); });
|
|
53
56
|
pixels.tiktok?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
54
|
-
initTikTokPixel(px.pixelId); });
|
|
57
|
+
loadPromises.push(initTikTokPixel(px.pixelId)); });
|
|
55
58
|
pixels.snapchat?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
56
|
-
initSnapchatPixel(px.pixelId); });
|
|
59
|
+
loadPromises.push(initSnapchatPixel(px.pixelId)); });
|
|
57
60
|
pixels.pinterest?.forEach((px) => { if (px.enabled && px.pixelId)
|
|
58
|
-
initPinterestPixel(px.pixelId); });
|
|
61
|
+
loadPromises.push(initPinterestPixel(px.pixelId)); });
|
|
59
62
|
pixels.gtm?.forEach((px) => { if (px.enabled && px.containerId)
|
|
60
|
-
initGTM(px.containerId); });
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
loadPromises.push(initGTM(px.containerId)); });
|
|
64
|
+
Promise.all(loadPromises).then(() => {
|
|
65
|
+
if (isMountedRef.current)
|
|
66
|
+
setPixelsInitialized(true);
|
|
67
|
+
});
|
|
63
68
|
}
|
|
64
69
|
catch (error) {
|
|
65
70
|
console.error('[SDK Pixels] Initialization error:', error);
|
|
@@ -73,8 +78,21 @@ export function PixelTrackingProvider({ children }) {
|
|
|
73
78
|
return;
|
|
74
79
|
try {
|
|
75
80
|
const events = resolvePixelEvents(pixels, eventName, parameters);
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
catch (error) {
|
|
@@ -105,7 +123,7 @@ export function usePixelTracking() {
|
|
|
105
123
|
// Browser-specific: fire an event to the correct global pixel function
|
|
106
124
|
// ---------------------------------------------------------------------------
|
|
107
125
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
108
|
-
function fire(provider, name, params) {
|
|
126
|
+
function fire(provider, name, params, pixel) {
|
|
109
127
|
if (typeof window === 'undefined')
|
|
110
128
|
return;
|
|
111
129
|
const w = window;
|
|
@@ -113,15 +131,19 @@ function fire(provider, name, params) {
|
|
|
113
131
|
case 'facebook':
|
|
114
132
|
w.fbq?.('track', name, params);
|
|
115
133
|
break;
|
|
116
|
-
case 'tiktok':
|
|
117
|
-
//
|
|
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;
|
|
118
139
|
if (name === 'Pageview') {
|
|
119
|
-
|
|
140
|
+
target?.page?.();
|
|
120
141
|
}
|
|
121
142
|
else {
|
|
122
|
-
|
|
143
|
+
target?.track?.(name, params);
|
|
123
144
|
}
|
|
124
145
|
break;
|
|
146
|
+
}
|
|
125
147
|
case 'snapchat':
|
|
126
148
|
w.snaptr?.('track', name, params);
|
|
127
149
|
break;
|
|
@@ -161,33 +183,54 @@ function fireGTM(event, params) {
|
|
|
161
183
|
// ===========================================================================
|
|
162
184
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
163
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;
|
|
164
199
|
function initMetaPixel(pixelId) {
|
|
165
|
-
if (!win
|
|
166
|
-
return;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
win.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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)
|
|
184
224
|
win.fbq('init', pixelId);
|
|
225
|
+
return _metaScriptEl ? waitForScriptLoad(_metaScriptEl) : Promise.resolve();
|
|
185
226
|
}
|
|
227
|
+
let _tiktokBaseInitialized = false;
|
|
186
228
|
function initTikTokPixel(pixelId) {
|
|
187
229
|
if (!win)
|
|
188
|
-
return;
|
|
189
|
-
// Initialize TikTok base code once
|
|
190
|
-
if (!
|
|
230
|
+
return Promise.resolve();
|
|
231
|
+
// Initialize TikTok base code once
|
|
232
|
+
if (!_tiktokBaseInitialized) {
|
|
233
|
+
_tiktokBaseInitialized = true;
|
|
191
234
|
win.TiktokAnalyticsObject = 'ttq';
|
|
192
235
|
const ttq = (win.ttq = win.ttq || []);
|
|
193
236
|
ttq.methods = [
|
|
@@ -225,57 +268,82 @@ function initTikTokPixel(pixelId) {
|
|
|
225
268
|
p?.parentNode?.insertBefore(s, p);
|
|
226
269
|
};
|
|
227
270
|
}
|
|
228
|
-
// Skip if this specific pixel ID is already
|
|
229
|
-
if (win.ttq
|
|
230
|
-
return;
|
|
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
|
|
231
275
|
win.ttq.load(pixelId);
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
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();
|
|
235
280
|
}
|
|
281
|
+
let _snapchatScriptEl = null;
|
|
236
282
|
function initSnapchatPixel(pixelId) {
|
|
237
|
-
if (!win
|
|
238
|
-
return;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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)
|
|
252
303
|
win.snaptr('init', pixelId);
|
|
304
|
+
return _snapchatScriptEl ? waitForScriptLoad(_snapchatScriptEl) : Promise.resolve();
|
|
253
305
|
}
|
|
306
|
+
let _pinterestScriptEl = null;
|
|
254
307
|
function initPinterestPixel(pixelId) {
|
|
255
|
-
if (!win
|
|
256
|
-
return;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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)
|
|
265
323
|
win.pintrk('load', pixelId);
|
|
266
324
|
// Note: pintrk('page') is NOT called here — the Provider's auto page-view
|
|
267
325
|
// effect handles it via fire(). Calling both would double-count page views.
|
|
326
|
+
return _pinterestScriptEl ? waitForScriptLoad(_pinterestScriptEl) : Promise.resolve();
|
|
268
327
|
}
|
|
269
328
|
function initGTM(containerId) {
|
|
270
329
|
if (!win || !containerId)
|
|
271
|
-
return;
|
|
330
|
+
return Promise.resolve();
|
|
272
331
|
const isGtmContainer = containerId.startsWith('GTM-');
|
|
273
332
|
const scriptUrlPart = isGtmContainer
|
|
274
333
|
? `googletagmanager.com/gtm.js?id=${containerId}`
|
|
275
334
|
: `googletagmanager.com/gtag/js?id=${containerId}`;
|
|
276
335
|
if (document.querySelector(`script[src*="${scriptUrlPart}"]`))
|
|
277
|
-
return;
|
|
336
|
+
return Promise.resolve();
|
|
278
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 */ }
|
|
279
347
|
if (isGtmContainer) {
|
|
280
348
|
win.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
|
|
281
349
|
const f = document.getElementsByTagName('script')[0];
|
|
@@ -295,13 +363,25 @@ function initGTM(containerId) {
|
|
|
295
363
|
iframe.style.visibility = 'hidden';
|
|
296
364
|
noscript.appendChild(iframe);
|
|
297
365
|
(document.body || document.getElementsByTagName('body')[0])?.insertBefore(noscript, document.body?.firstChild ?? null);
|
|
366
|
+
return waitForScriptLoad(j);
|
|
298
367
|
}
|
|
299
368
|
else {
|
|
300
369
|
if (!win.gtag) {
|
|
301
370
|
win.gtag = function (..._args) { win.dataLayer.push(arguments); };
|
|
302
371
|
}
|
|
303
372
|
win.gtag('js', new Date());
|
|
304
|
-
|
|
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 });
|
|
305
385
|
const script = document.createElement('script');
|
|
306
386
|
script.async = true;
|
|
307
387
|
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + containerId;
|
|
@@ -310,5 +390,6 @@ function initGTM(containerId) {
|
|
|
310
390
|
firstScript.parentNode.insertBefore(script, firstScript);
|
|
311
391
|
else
|
|
312
392
|
(document.head || document.getElementsByTagName('head')[0])?.appendChild(script);
|
|
393
|
+
return waitForScriptLoad(script);
|
|
313
394
|
}
|
|
314
395
|
}
|
|
@@ -150,6 +150,24 @@ export function usePostPurchasesQuery(options) {
|
|
|
150
150
|
});
|
|
151
151
|
// Fetch order summary with variant options
|
|
152
152
|
await fetchOrderSummary(offerId, sessionId);
|
|
153
|
+
// Detect Whop payment method for this checkout session
|
|
154
|
+
try {
|
|
155
|
+
const paymentMethods = await postPurchasesResource.getPaymentMethods(sessionId);
|
|
156
|
+
const isWhop = paymentMethods.some((m) => m.type === 'whop');
|
|
157
|
+
if (isWhop) {
|
|
158
|
+
setCheckoutSessions(prev => ({
|
|
159
|
+
...prev,
|
|
160
|
+
[offerId]: {
|
|
161
|
+
...prev[offerId],
|
|
162
|
+
isWhop: true,
|
|
163
|
+
}
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
// Non-critical: if payment methods fetch fails, fall back to standard flow
|
|
169
|
+
console.warn(`[SDK] Failed to detect payment methods for offer ${offerId}:`, err);
|
|
170
|
+
}
|
|
153
171
|
}
|
|
154
172
|
finally {
|
|
155
173
|
// Remove from initializing set
|
|
@@ -421,8 +439,22 @@ export function usePostPurchasesQuery(options) {
|
|
|
421
439
|
if (!sessionState?.checkoutSessionId) {
|
|
422
440
|
throw new Error('Checkout session not initialized for this offer');
|
|
423
441
|
}
|
|
424
|
-
|
|
425
|
-
|
|
442
|
+
if (sessionState.isWhop) {
|
|
443
|
+
// Whop post-purchase: charge using the stored payment method from the original order
|
|
444
|
+
const storeId = session?.storeId;
|
|
445
|
+
if (!storeId) {
|
|
446
|
+
throw new Error('Store ID not available for Whop payment');
|
|
447
|
+
}
|
|
448
|
+
await postPurchasesResource.chargeWhopPostPurchase({
|
|
449
|
+
checkoutSessionId: sessionState.checkoutSessionId,
|
|
450
|
+
orderId,
|
|
451
|
+
storeId,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// Standard post-purchase: use the generic pay endpoint
|
|
456
|
+
await postPurchasesResource.payWithCheckoutSession(sessionState.checkoutSessionId, orderId);
|
|
457
|
+
}
|
|
426
458
|
},
|
|
427
459
|
};
|
|
428
460
|
}
|
|
@@ -12,7 +12,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
12
12
|
import { OffersResource } from '../../core/resources/offers';
|
|
13
13
|
import { getGlobalApiClient } from './useApiQuery';
|
|
14
14
|
export function usePreviewOffer(options) {
|
|
15
|
-
const { offerId, currency: requestedCurrency = '
|
|
15
|
+
const { offerId, currency: requestedCurrency = '', initialSelections = {} } = options;
|
|
16
16
|
const queryParamsCurrency = typeof window !== 'undefined'
|
|
17
17
|
? new URLSearchParams(window.location.search).get('currency') || undefined
|
|
18
18
|
: undefined;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook to extract URL parameters that works with both remapped and non-remapped paths
|
|
3
|
-
*
|
|
4
|
-
* This hook automatically detects if the current path is remapped and extracts
|
|
5
|
-
* parameters correctly in both cases, so your components don't need to know
|
|
6
|
-
* about path remapping at all.
|
|
2
|
+
* Hook to extract URL parameters that works with both remapped and non-remapped paths.
|
|
3
|
+
* Router-agnostic: uses window.location + path-to-regexp directly (no react-router-dom needed).
|
|
7
4
|
*
|
|
8
5
|
* @param internalPath - The internal path pattern (e.g., "/hello-with-param/:myparam")
|
|
9
6
|
* @returns Parameters object with extracted values
|
|
@@ -13,7 +10,6 @@
|
|
|
13
10
|
* function HelloWithParamPage() {
|
|
14
11
|
* // Works for both /hello-with-param/test AND /myremap/test
|
|
15
12
|
* const { myparam } = useRemappableParams<{ myparam: string }>('/hello-with-param/:myparam');
|
|
16
|
-
*
|
|
17
13
|
* return <div>Parameter: {myparam}</div>;
|
|
18
14
|
* }
|
|
19
15
|
* ```
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { useParams } from 'react-router-dom';
|
|
2
1
|
import { getPathInfo } from '../../core/pathRemapping';
|
|
3
2
|
import { match } from 'path-to-regexp';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
* Extract params from a URL path using a pattern via path-to-regexp
|
|
5
|
+
*/
|
|
6
|
+
function extractMatchParams(pattern, path) {
|
|
7
|
+
try {
|
|
8
|
+
const matchFn = match(pattern, { decode: decodeURIComponent });
|
|
9
|
+
const result = matchFn(path);
|
|
10
|
+
if (result && typeof result !== 'boolean') {
|
|
11
|
+
return result.params;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch { /* ignore */ }
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hook to extract URL parameters that works with both remapped and non-remapped paths.
|
|
19
|
+
* Router-agnostic: uses window.location + path-to-regexp directly (no react-router-dom needed).
|
|
10
20
|
*
|
|
11
21
|
* @param internalPath - The internal path pattern (e.g., "/hello-with-param/:myparam")
|
|
12
22
|
* @returns Parameters object with extracted values
|
|
@@ -16,21 +26,19 @@ import { match } from 'path-to-regexp';
|
|
|
16
26
|
* function HelloWithParamPage() {
|
|
17
27
|
* // Works for both /hello-with-param/test AND /myremap/test
|
|
18
28
|
* const { myparam } = useRemappableParams<{ myparam: string }>('/hello-with-param/:myparam');
|
|
19
|
-
*
|
|
20
29
|
* return <div>Parameter: {myparam}</div>;
|
|
21
30
|
* }
|
|
22
31
|
* ```
|
|
23
32
|
*/
|
|
24
33
|
export function useRemappableParams(internalPath) {
|
|
25
|
-
const routerParams = useParams();
|
|
26
34
|
const pathInfo = getPathInfo();
|
|
27
|
-
|
|
35
|
+
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '/';
|
|
36
|
+
// If not remapped, extract params from URL using the internal pattern directly
|
|
28
37
|
if (!pathInfo.isRemapped) {
|
|
29
|
-
return
|
|
38
|
+
return extractMatchParams(internalPath, currentPath) ?? {};
|
|
30
39
|
}
|
|
31
40
|
// If remapped, extract params from the external URL
|
|
32
41
|
try {
|
|
33
|
-
// Get the external pattern from localStorage (for testing) or from meta tag
|
|
34
42
|
let externalPattern = null;
|
|
35
43
|
// Check localStorage for explicit remapping (development/testing)
|
|
36
44
|
if (typeof localStorage !== 'undefined') {
|
|
@@ -54,17 +62,13 @@ export function useRemappableParams(internalPath) {
|
|
|
54
62
|
externalPattern = metaTag.getAttribute('content');
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
|
-
// If we have an external pattern, extract params
|
|
65
|
+
// If we have an external pattern, extract params and map to internal names
|
|
58
66
|
if (externalPattern) {
|
|
59
67
|
const matchFn = match(externalPattern, { decode: decodeURIComponent });
|
|
60
68
|
const result = matchFn(pathInfo.externalPath);
|
|
61
69
|
if (result && typeof result !== 'boolean') {
|
|
62
|
-
// We have extracted params from external URL
|
|
63
|
-
// Now we need to map them to internal param names
|
|
64
|
-
// Extract param names from both patterns
|
|
65
70
|
const externalParamNames = extractParamNames(externalPattern);
|
|
66
71
|
const internalParamNames = extractParamNames(internalPath);
|
|
67
|
-
// Map external params to internal params (by position)
|
|
68
72
|
const mappedParams = {};
|
|
69
73
|
externalParamNames.forEach((externalName, index) => {
|
|
70
74
|
const internalName = internalParamNames[index];
|
|
@@ -76,17 +80,13 @@ export function useRemappableParams(internalPath) {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
// Fallback: try to extract from internal path directly
|
|
79
|
-
|
|
80
|
-
const result = matchFn(pathInfo.externalPath);
|
|
81
|
-
if (result && typeof result !== 'boolean') {
|
|
82
|
-
return result.params;
|
|
83
|
-
}
|
|
83
|
+
return extractMatchParams(internalPath, pathInfo.externalPath) ?? {};
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
86
86
|
console.error('[useRemappableParams] Failed to extract params:', error);
|
|
87
87
|
}
|
|
88
|
-
//
|
|
89
|
-
return
|
|
88
|
+
// Final fallback: try matching current URL against internal pattern
|
|
89
|
+
return extractMatchParams(internalPath, currentPath) ?? {};
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Extract parameter names from a path pattern
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for setting the payment method on a checkout session
|
|
3
|
+
*/
|
|
4
|
+
import { CheckoutData } from '../../core/resources/checkout';
|
|
5
|
+
import type { PaymentMethodName } from '../../core/resources/checkout';
|
|
6
|
+
export interface UseSetPaymentMethodOptions {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
checkout?: CheckoutData;
|
|
9
|
+
onSuccess?: (paymentMethodName: PaymentMethodName) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseSetPaymentMethodResult {
|
|
12
|
+
setPaymentMethod: (paymentMethodName: PaymentMethodName) => Promise<void>;
|
|
13
|
+
isPending: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function useSetPaymentMethod(options?: UseSetPaymentMethodOptions): UseSetPaymentMethodResult;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for setting the payment method on a checkout session
|
|
3
|
+
*/
|
|
4
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { CheckoutResource } from '../../core/resources/checkout';
|
|
7
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
8
|
+
export function useSetPaymentMethod(options = {}) {
|
|
9
|
+
const { sessionId, checkout, onSuccess } = options;
|
|
10
|
+
const queryClient = useQueryClient();
|
|
11
|
+
const checkoutResource = useMemo(() => new CheckoutResource(getGlobalApiClient()), []);
|
|
12
|
+
const effectiveSessionId = sessionId ?? checkout?.checkoutSession?.id;
|
|
13
|
+
const mutation = useMutation({
|
|
14
|
+
mutationFn: (paymentMethodName) => {
|
|
15
|
+
if (!effectiveSessionId) {
|
|
16
|
+
throw new Error('No session ID available');
|
|
17
|
+
}
|
|
18
|
+
return checkoutResource.setPaymentMethod(effectiveSessionId, paymentMethodName);
|
|
19
|
+
},
|
|
20
|
+
onSuccess: async (_, paymentMethodName) => {
|
|
21
|
+
await queryClient.invalidateQueries({ queryKey: ['checkout'] });
|
|
22
|
+
onSuccess?.(paymentMethodName);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const setPaymentMethod = async (paymentMethodName) => {
|
|
26
|
+
await mutation.mutateAsync(paymentMethodName);
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
setPaymentMethod,
|
|
30
|
+
isPending: mutation.isPending,
|
|
31
|
+
error: mutation.error,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
12
|
* ```tsx
|
|
13
|
-
* const { stepConfig, paymentFlowId,
|
|
13
|
+
* const { stepConfig, paymentFlowId, resources } = useStepConfig();
|
|
14
14
|
*
|
|
15
15
|
* // Access payment flow override
|
|
16
16
|
* if (paymentFlowId) {
|
|
17
17
|
* console.log('Using custom payment flow:', paymentFlowId);
|
|
18
18
|
* }
|
|
19
19
|
*
|
|
20
|
-
* // Access
|
|
21
|
-
* const offerId =
|
|
20
|
+
* // Access resource bindings (e.g., offer ID for current A/B variant)
|
|
21
|
+
* const offerId = resources?.offer;
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import { PixelsConfig, RuntimeStepConfig } from '../../core/funnelClient';
|
|
24
|
+
import { type PaymentSetupConfig, type PixelsConfig, type RuntimeStepConfig } from '../../core/funnelClient';
|
|
25
25
|
export interface UseStepConfigResult {
|
|
26
26
|
/**
|
|
27
27
|
* Full step configuration object
|
|
@@ -34,10 +34,11 @@ export interface UseStepConfigResult {
|
|
|
34
34
|
*/
|
|
35
35
|
paymentFlowId: string | undefined;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
* For A/B tests, this contains the resources for the specific variant the user landed on
|
|
37
|
+
* Resource bindings for this step/variant.
|
|
39
38
|
* e.g., { offer: 'offer_xxx', product: 'product_xxx' }
|
|
40
39
|
*/
|
|
40
|
+
resources: Record<string, string> | undefined;
|
|
41
|
+
/** @deprecated Use `resources` instead */
|
|
41
42
|
staticResources: Record<string, string> | undefined;
|
|
42
43
|
/**
|
|
43
44
|
* Get scripts for a specific injection position
|
|
@@ -46,6 +47,22 @@ export interface UseStepConfigResult {
|
|
|
46
47
|
*/
|
|
47
48
|
getScripts: (position?: 'head-start' | 'head-end' | 'body-start' | 'body-end') => RuntimeStepConfig['scripts'];
|
|
48
49
|
pixels: PixelsConfig | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Payment setup configuration for this step.
|
|
52
|
+
* Keys: "{method}" or "{method}:{provider}" (e.g. "card", "apple_pay:stripe")
|
|
53
|
+
* Use helpers: getEnabledMethods(), getExpressMethods(), findMethod()
|
|
54
|
+
*/
|
|
55
|
+
paymentSetupConfig: PaymentSetupConfig | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Enabled order bump offer IDs for this step.
|
|
58
|
+
* undefined = inherit all store bumps, string[] = only these IDs.
|
|
59
|
+
*/
|
|
60
|
+
orderBumpOfferIds: string[] | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Enabled upsell offer IDs for this step.
|
|
63
|
+
* undefined = inherit all store upsells, string[] = only these IDs.
|
|
64
|
+
*/
|
|
65
|
+
upsellOfferIds: string[] | undefined;
|
|
49
66
|
}
|
|
50
67
|
/**
|
|
51
68
|
* Hook to access runtime step configuration injected via HTML
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
12
|
* ```tsx
|
|
13
|
-
* const { stepConfig, paymentFlowId,
|
|
13
|
+
* const { stepConfig, paymentFlowId, resources } = useStepConfig();
|
|
14
14
|
*
|
|
15
15
|
* // Access payment flow override
|
|
16
16
|
* if (paymentFlowId) {
|
|
17
17
|
* console.log('Using custom payment flow:', paymentFlowId);
|
|
18
18
|
* }
|
|
19
19
|
*
|
|
20
|
-
* // Access
|
|
21
|
-
* const offerId =
|
|
20
|
+
* // Access resource bindings (e.g., offer ID for current A/B variant)
|
|
21
|
+
* const offerId = resources?.offer;
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import { useMemo } from 'react';
|
|
25
|
-
import { getAssignedPaymentFlowId, getAssignedPixels,
|
|
25
|
+
import { 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
|
*
|
|
@@ -35,7 +35,7 @@ export function useStepConfig() {
|
|
|
35
35
|
// Read once on mount - these values don't change during the page lifecycle
|
|
36
36
|
const stepConfig = useMemo(() => getAssignedStepConfig(), []);
|
|
37
37
|
const paymentFlowId = useMemo(() => getAssignedPaymentFlowId(), []);
|
|
38
|
-
const
|
|
38
|
+
const resources = useMemo(() => getAssignedResources(), []);
|
|
39
39
|
const pixels = useMemo(() => getAssignedPixels(), []);
|
|
40
40
|
// Create a stable reference for getScripts
|
|
41
41
|
const getScripts = useMemo(() => {
|
|
@@ -43,11 +43,18 @@ export function useStepConfig() {
|
|
|
43
43
|
return getAssignedScripts(position);
|
|
44
44
|
};
|
|
45
45
|
}, []);
|
|
46
|
+
const paymentSetupConfig = useMemo(() => stepConfig?.paymentSetupConfig, [stepConfig]);
|
|
47
|
+
const orderBumpOfferIds = useMemo(() => getAssignedOrderBumpOfferIds(), []);
|
|
48
|
+
const upsellOfferIds = useMemo(() => getAssignedUpsellOfferIds(), []);
|
|
46
49
|
return {
|
|
47
50
|
stepConfig,
|
|
48
51
|
paymentFlowId,
|
|
49
|
-
|
|
52
|
+
resources,
|
|
53
|
+
staticResources: resources,
|
|
50
54
|
getScripts,
|
|
51
|
-
pixels
|
|
55
|
+
pixels,
|
|
56
|
+
paymentSetupConfig,
|
|
57
|
+
orderBumpOfferIds,
|
|
58
|
+
upsellOfferIds,
|
|
52
59
|
};
|
|
53
60
|
}
|