@tagadapay/plugin-sdk 3.0.3 → 3.0.9
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 +113 -0
- package/dist/config/basisTheory.d.ts +26 -0
- package/dist/config/basisTheory.js +29 -0
- package/dist/external-tracker.js +4947 -0
- package/dist/external-tracker.min.js +11 -0
- package/dist/external-tracker.min.js.map +7 -0
- package/dist/react/config/payment.d.ts +8 -8
- package/dist/react/config/payment.js +17 -21
- package/dist/react/hooks/useApplePay.js +1 -1
- package/dist/react/hooks/usePayment.js +1 -3
- package/dist/react/hooks/useThreeds.js +2 -2
- package/dist/v2/core/client.d.ts +30 -3
- package/dist/v2/core/client.js +219 -8
- package/dist/v2/core/config/environment.d.ts +16 -3
- package/dist/v2/core/config/environment.js +72 -3
- package/dist/v2/core/funnelClient.d.ts +4 -0
- package/dist/v2/core/funnelClient.js +106 -4
- package/dist/v2/core/resources/funnel.d.ts +22 -0
- package/dist/v2/core/resources/offers.d.ts +64 -3
- package/dist/v2/core/resources/offers.js +112 -10
- package/dist/v2/core/resources/postPurchases.js +4 -1
- package/dist/v2/core/utils/configHotReload.d.ts +39 -0
- package/dist/v2/core/utils/configHotReload.js +75 -0
- package/dist/v2/core/utils/eventBus.d.ts +11 -0
- package/dist/v2/core/utils/eventBus.js +34 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +14 -5
- package/dist/v2/core/utils/pluginConfig.js +74 -59
- package/dist/v2/core/utils/previewMode.d.ts +114 -0
- package/dist/v2/core/utils/previewMode.js +379 -0
- package/dist/v2/core/utils/sessionStorage.d.ts +5 -0
- package/dist/v2/core/utils/sessionStorage.js +22 -0
- package/dist/v2/index.d.ts +4 -1
- package/dist/v2/index.js +3 -1
- package/dist/v2/react/hooks/useOfferQuery.js +50 -17
- package/dist/v2/react/hooks/usePaymentQuery.js +1 -3
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +84 -0
- package/dist/v2/react/hooks/usePreviewOffer.js +290 -0
- package/dist/v2/react/hooks/useThreeds.js +2 -2
- package/dist/v2/react/index.d.ts +2 -0
- package/dist/v2/react/index.js +1 -0
- package/dist/v2/react/providers/TagadaProvider.js +49 -32
- package/dist/v2/standalone/external-tracker.d.ts +119 -0
- package/dist/v2/standalone/external-tracker.js +260 -0
- package/dist/v2/standalone/index.d.ts +2 -0
- package/dist/v2/standalone/index.js +6 -0
- package/package.json +11 -3
- package/dist/v2/react/hooks/useOffersQuery.d.ts +0 -12
- package/dist/v2/react/hooks/useOffersQuery.js +0 -404
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Override Parameters
|
|
3
|
+
*
|
|
4
|
+
* Centralized parameter management for SDK behavior overrides.
|
|
5
|
+
* Parameters can come from URL query params, localStorage, or cookies.
|
|
6
|
+
*
|
|
7
|
+
* Key concepts:
|
|
8
|
+
* - forceReset: Clears all stored state (localStorage, cookies) - simulates hard refresh
|
|
9
|
+
* - draft: Marks customers/sessions as draft (for staging/preview/testing)
|
|
10
|
+
* - funnelTracking: Controls whether funnel events are tracked
|
|
11
|
+
* - tagadaClientEnv: Forces specific environment (production/development/local) - overrides auto-detection
|
|
12
|
+
* - tagadaClientBaseUrl: Forces custom API base URL - overrides environment-based URL
|
|
13
|
+
* - token: Authentication token (URL > localStorage)
|
|
14
|
+
* - funnelSessionId: Active funnel session (URL > cookie)
|
|
15
|
+
*
|
|
16
|
+
* Usage examples:
|
|
17
|
+
* - Force production API: ?tagadaClientEnv=production
|
|
18
|
+
* - Force development API: ?tagadaClientEnv=development
|
|
19
|
+
* - Force local API: ?tagadaClientEnv=local
|
|
20
|
+
* - Custom API URL: ?tagadaClientBaseUrl=https://tagada.loclx.io
|
|
21
|
+
* - Combined: ?tagadaClientEnv=local&tagadaClientBaseUrl=https://tagada.loclx.io
|
|
22
|
+
* - Hard reset + production: ?forceReset=true&tagadaClientEnv=production
|
|
23
|
+
*/
|
|
24
|
+
import { clearClientToken, setClientToken, getClientToken } from './tokenStorage';
|
|
25
|
+
import { clearFunnelSessionCookie } from './sessionStorage';
|
|
26
|
+
// Storage keys for SDK override params
|
|
27
|
+
const STORAGE_KEYS = {
|
|
28
|
+
DRAFT: 'tgd_draft',
|
|
29
|
+
FUNNEL_TRACKING: 'tgd_funnel_tracking',
|
|
30
|
+
FORCE_RESET: 'tgd_force_reset',
|
|
31
|
+
CLIENT_ENV: 'tgd_client_env',
|
|
32
|
+
CLIENT_BASE_URL: 'tgd_client_base_url',
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Check if force reset is active (simulates hard refresh)
|
|
36
|
+
*/
|
|
37
|
+
export function isForceReset() {
|
|
38
|
+
if (typeof window === 'undefined')
|
|
39
|
+
return false;
|
|
40
|
+
const params = new URLSearchParams(window.location.search);
|
|
41
|
+
return params.get('forceReset') === 'true';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get value from localStorage with fallback
|
|
45
|
+
*/
|
|
46
|
+
function getFromStorage(key) {
|
|
47
|
+
if (typeof window === 'undefined')
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
return localStorage.getItem(key);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Set value in localStorage
|
|
58
|
+
*/
|
|
59
|
+
function setInStorage(key, value) {
|
|
60
|
+
if (typeof window === 'undefined')
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
localStorage.setItem(key, value);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Storage not available
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get value from cookie
|
|
71
|
+
*/
|
|
72
|
+
function getFromCookie(key) {
|
|
73
|
+
if (typeof document === 'undefined')
|
|
74
|
+
return null;
|
|
75
|
+
const cookies = document.cookie.split(';');
|
|
76
|
+
const cookie = cookies.find(c => c.trim().startsWith(`${key}=`));
|
|
77
|
+
return cookie ? cookie.split('=')[1]?.trim() : null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set value in cookie
|
|
81
|
+
*/
|
|
82
|
+
function setInCookie(key, value, maxAge = 86400) {
|
|
83
|
+
if (typeof document === 'undefined')
|
|
84
|
+
return;
|
|
85
|
+
document.cookie = `${key}=${value}; path=/; max-age=${maxAge}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clear value from cookie
|
|
89
|
+
*/
|
|
90
|
+
function clearFromCookie(key) {
|
|
91
|
+
if (typeof document === 'undefined')
|
|
92
|
+
return;
|
|
93
|
+
document.cookie = `${key}=; path=/; max-age=0`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get SDK override parameters from all sources (URL, localStorage, cookies)
|
|
97
|
+
* Priority: URL > localStorage > Cookie
|
|
98
|
+
*/
|
|
99
|
+
export function getSDKParams() {
|
|
100
|
+
if (typeof window === 'undefined') {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
104
|
+
// Get draft mode (URL > localStorage > cookie)
|
|
105
|
+
let draft;
|
|
106
|
+
const urlDraft = urlParams.get('draft');
|
|
107
|
+
if (urlDraft !== null) {
|
|
108
|
+
draft = urlDraft === 'true';
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const storageDraft = getFromStorage(STORAGE_KEYS.DRAFT) || getFromCookie(STORAGE_KEYS.DRAFT);
|
|
112
|
+
if (storageDraft !== null) {
|
|
113
|
+
draft = storageDraft === 'true';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Get funnel tracking (URL > localStorage > cookie, default true)
|
|
117
|
+
let funnelTracking;
|
|
118
|
+
const urlTracking = urlParams.get('funnelTracking');
|
|
119
|
+
if (urlTracking !== null) {
|
|
120
|
+
funnelTracking = urlTracking !== 'false';
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const storageTracking = getFromStorage(STORAGE_KEYS.FUNNEL_TRACKING) || getFromCookie(STORAGE_KEYS.FUNNEL_TRACKING);
|
|
124
|
+
if (storageTracking !== null) {
|
|
125
|
+
funnelTracking = storageTracking !== 'false';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Get token (URL > localStorage)
|
|
129
|
+
const token = urlParams.get('token') || getClientToken() || null;
|
|
130
|
+
// Get funnel session ID (URL only, cookie handled elsewhere)
|
|
131
|
+
const funnelSessionId = urlParams.get('funnelSessionId') || null;
|
|
132
|
+
// Get funnel ID (URL only)
|
|
133
|
+
const funnelId = urlParams.get('funnelId') || null;
|
|
134
|
+
// Force reset is URL-only (not persisted)
|
|
135
|
+
const forceReset = urlParams.get('forceReset') === 'true';
|
|
136
|
+
// Get client environment override (URL > localStorage > cookie)
|
|
137
|
+
let tagadaClientEnv;
|
|
138
|
+
const urlEnv = urlParams.get('tagadaClientEnv');
|
|
139
|
+
if (urlEnv && (urlEnv === 'production' || urlEnv === 'development' || urlEnv === 'local')) {
|
|
140
|
+
tagadaClientEnv = urlEnv;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const storageEnv = getFromStorage(STORAGE_KEYS.CLIENT_ENV) || getFromCookie(STORAGE_KEYS.CLIENT_ENV);
|
|
144
|
+
if (storageEnv && (storageEnv === 'production' || storageEnv === 'development' || storageEnv === 'local')) {
|
|
145
|
+
tagadaClientEnv = storageEnv;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Get custom API base URL override (URL > localStorage > cookie)
|
|
149
|
+
let tagadaClientBaseUrl;
|
|
150
|
+
const urlBaseUrl = urlParams.get('tagadaClientBaseUrl');
|
|
151
|
+
if (urlBaseUrl) {
|
|
152
|
+
tagadaClientBaseUrl = urlBaseUrl;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const storageBaseUrl = getFromStorage(STORAGE_KEYS.CLIENT_BASE_URL) || getFromCookie(STORAGE_KEYS.CLIENT_BASE_URL);
|
|
156
|
+
if (storageBaseUrl) {
|
|
157
|
+
tagadaClientBaseUrl = storageBaseUrl;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
forceReset,
|
|
162
|
+
token,
|
|
163
|
+
funnelSessionId,
|
|
164
|
+
funnelId,
|
|
165
|
+
draft,
|
|
166
|
+
funnelTracking,
|
|
167
|
+
tagadaClientEnv,
|
|
168
|
+
tagadaClientBaseUrl,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Legacy alias for backward compatibility
|
|
173
|
+
* @deprecated Use getSDKParams() instead
|
|
174
|
+
*/
|
|
175
|
+
export function getPreviewParams() {
|
|
176
|
+
return getSDKParams();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if draft mode is active
|
|
180
|
+
* Uses centralized getSDKParams()
|
|
181
|
+
*/
|
|
182
|
+
export function isDraftMode() {
|
|
183
|
+
const params = getSDKParams();
|
|
184
|
+
return params.draft ?? false;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Set draft mode in storage for persistence
|
|
188
|
+
*/
|
|
189
|
+
export function setDraftMode(draft) {
|
|
190
|
+
if (typeof window === 'undefined')
|
|
191
|
+
return;
|
|
192
|
+
if (draft) {
|
|
193
|
+
setInStorage(STORAGE_KEYS.DRAFT, 'true');
|
|
194
|
+
setInCookie(STORAGE_KEYS.DRAFT, 'true', 86400); // 24 hours
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
setInStorage(STORAGE_KEYS.DRAFT, 'false');
|
|
198
|
+
clearFromCookie(STORAGE_KEYS.DRAFT);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Initialize SDK with override parameters
|
|
203
|
+
*
|
|
204
|
+
* This function handles SDK initialization based on override params:
|
|
205
|
+
* 1. If forceReset flag is set: Clear all stored state (simulates hard refresh)
|
|
206
|
+
* 2. If token in URL: Use it (override localStorage)
|
|
207
|
+
* 3. If draft in URL: Persist it to storage
|
|
208
|
+
* 4. If funnelTracking in URL: Persist it to storage
|
|
209
|
+
*
|
|
210
|
+
* @returns True if force reset was activated and state was cleared
|
|
211
|
+
*/
|
|
212
|
+
export function handlePreviewMode(debugMode = false) {
|
|
213
|
+
const params = getSDKParams();
|
|
214
|
+
const shouldReset = params.forceReset || false;
|
|
215
|
+
if (!shouldReset && !params.token) {
|
|
216
|
+
// Not forcing reset and no explicit token override
|
|
217
|
+
// But still persist draft/tracking if in URL
|
|
218
|
+
persistSDKParamsFromURL();
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (debugMode) {
|
|
222
|
+
console.log('[SDK] Detected params:', params);
|
|
223
|
+
}
|
|
224
|
+
// CASE 1: Force reset - clear all state (simulates hard refresh)
|
|
225
|
+
if (shouldReset) {
|
|
226
|
+
if (debugMode) {
|
|
227
|
+
console.log('[SDK] Force reset: Clearing all stored state');
|
|
228
|
+
}
|
|
229
|
+
clearClientToken();
|
|
230
|
+
clearFunnelSessionCookie();
|
|
231
|
+
// Clear all tagadapay-related localStorage keys
|
|
232
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
233
|
+
const keys = Object.keys(localStorage);
|
|
234
|
+
keys.forEach(key => {
|
|
235
|
+
if (key.startsWith('tagadapay_') || key.startsWith('tgd_')) {
|
|
236
|
+
if (debugMode) {
|
|
237
|
+
console.log(`[SDK] Clearing localStorage: ${key}`);
|
|
238
|
+
}
|
|
239
|
+
localStorage.removeItem(key);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// CASE 2: Token in URL - override stored token
|
|
245
|
+
if (params.token !== null && params.token !== undefined) {
|
|
246
|
+
if (debugMode) {
|
|
247
|
+
console.log('[SDK] Using token from URL:', params.token.substring(0, 20) + '...');
|
|
248
|
+
}
|
|
249
|
+
if (params.token === '' || params.token === 'null') {
|
|
250
|
+
// Explicitly cleared token
|
|
251
|
+
clearClientToken();
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Set token from URL
|
|
255
|
+
setClientToken(params.token);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else if (shouldReset) {
|
|
259
|
+
// Force reset but no token = clear stored token
|
|
260
|
+
if (debugMode) {
|
|
261
|
+
console.log('[SDK] Force reset mode (no token)');
|
|
262
|
+
}
|
|
263
|
+
clearClientToken();
|
|
264
|
+
}
|
|
265
|
+
// CASE 3: Persist URL params to storage
|
|
266
|
+
persistSDKParamsFromURL();
|
|
267
|
+
// CASE 4: Funnel session ID in URL - will be used by FunnelClient
|
|
268
|
+
if (params.funnelSessionId && debugMode) {
|
|
269
|
+
console.log('[SDK] Using funnelSessionId from URL:', params.funnelSessionId);
|
|
270
|
+
}
|
|
271
|
+
return shouldReset;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Persist SDK parameters from URL to storage
|
|
275
|
+
* This ensures params survive page navigation
|
|
276
|
+
*/
|
|
277
|
+
function persistSDKParamsFromURL() {
|
|
278
|
+
if (typeof window === 'undefined')
|
|
279
|
+
return;
|
|
280
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
281
|
+
// Persist draft mode if in URL
|
|
282
|
+
const urlDraft = urlParams.get('draft');
|
|
283
|
+
if (urlDraft !== null) {
|
|
284
|
+
setDraftMode(urlDraft === 'true');
|
|
285
|
+
}
|
|
286
|
+
// Persist funnel tracking if in URL
|
|
287
|
+
const urlTracking = urlParams.get('funnelTracking');
|
|
288
|
+
if (urlTracking !== null) {
|
|
289
|
+
setFunnelTracking(urlTracking !== 'false');
|
|
290
|
+
}
|
|
291
|
+
// Persist client environment if in URL
|
|
292
|
+
const urlEnv = urlParams.get('tagadaClientEnv');
|
|
293
|
+
if (urlEnv && (urlEnv === 'production' || urlEnv === 'development' || urlEnv === 'local')) {
|
|
294
|
+
setClientEnvironment(urlEnv);
|
|
295
|
+
}
|
|
296
|
+
// Persist custom base URL if in URL
|
|
297
|
+
const urlBaseUrl = urlParams.get('tagadaClientBaseUrl');
|
|
298
|
+
if (urlBaseUrl) {
|
|
299
|
+
setClientBaseUrl(urlBaseUrl);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Set funnel tracking mode in storage for persistence
|
|
304
|
+
*/
|
|
305
|
+
export function setFunnelTracking(enabled) {
|
|
306
|
+
if (typeof window === 'undefined')
|
|
307
|
+
return;
|
|
308
|
+
const value = enabled ? 'true' : 'false';
|
|
309
|
+
setInStorage(STORAGE_KEYS.FUNNEL_TRACKING, value);
|
|
310
|
+
setInCookie(STORAGE_KEYS.FUNNEL_TRACKING, value, 86400); // 24 hours
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Set client environment override in storage for persistence
|
|
314
|
+
*/
|
|
315
|
+
export function setClientEnvironment(env) {
|
|
316
|
+
if (typeof window === 'undefined')
|
|
317
|
+
return;
|
|
318
|
+
setInStorage(STORAGE_KEYS.CLIENT_ENV, env);
|
|
319
|
+
setInCookie(STORAGE_KEYS.CLIENT_ENV, env, 86400); // 24 hours
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Clear client environment override
|
|
323
|
+
*/
|
|
324
|
+
export function clearClientEnvironment() {
|
|
325
|
+
if (typeof window === 'undefined')
|
|
326
|
+
return;
|
|
327
|
+
try {
|
|
328
|
+
localStorage.removeItem(STORAGE_KEYS.CLIENT_ENV);
|
|
329
|
+
clearFromCookie(STORAGE_KEYS.CLIENT_ENV);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Storage not available
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Set custom API base URL override in storage for persistence
|
|
337
|
+
*/
|
|
338
|
+
export function setClientBaseUrl(baseUrl) {
|
|
339
|
+
if (typeof window === 'undefined')
|
|
340
|
+
return;
|
|
341
|
+
setInStorage(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl);
|
|
342
|
+
setInCookie(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl, 86400); // 24 hours
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Clear custom API base URL override
|
|
346
|
+
*/
|
|
347
|
+
export function clearClientBaseUrl() {
|
|
348
|
+
if (typeof window === 'undefined')
|
|
349
|
+
return;
|
|
350
|
+
try {
|
|
351
|
+
localStorage.removeItem(STORAGE_KEYS.CLIENT_BASE_URL);
|
|
352
|
+
clearFromCookie(STORAGE_KEYS.CLIENT_BASE_URL);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Storage not available
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Check if we should use URL params over stored values
|
|
360
|
+
*
|
|
361
|
+
* Returns true if:
|
|
362
|
+
* - Force reset is active, OR
|
|
363
|
+
* - Explicit token/sessionId in URL (indicating intentional override)
|
|
364
|
+
*/
|
|
365
|
+
export function shouldUseUrlParams() {
|
|
366
|
+
const params = getSDKParams();
|
|
367
|
+
return !!(params.forceReset ||
|
|
368
|
+
params.token !== null ||
|
|
369
|
+
params.funnelSessionId !== null);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Check if funnel tracking is enabled
|
|
373
|
+
* Uses centralized getSDKParams()
|
|
374
|
+
* Default is true for normal operations
|
|
375
|
+
*/
|
|
376
|
+
export function isFunnelTrackingEnabled() {
|
|
377
|
+
const params = getSDKParams();
|
|
378
|
+
return params.funnelTracking ?? true; // Default true
|
|
379
|
+
}
|
|
@@ -18,3 +18,8 @@ export declare function clearFunnelSessionCookie(): void;
|
|
|
18
18
|
* Check if a funnel session cookie exists
|
|
19
19
|
*/
|
|
20
20
|
export declare function hasFunnelSessionCookie(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Retrieve funnel variant ID from sticky session (for A/B tracking)
|
|
23
|
+
* The variant ID is stored in the sticky session metadata by plugin-routing
|
|
24
|
+
*/
|
|
25
|
+
export declare function getFunnelVariantId(): string | undefined;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Handles session persistence using browser cookies
|
|
4
4
|
*/
|
|
5
5
|
const FUNNEL_SESSION_COOKIE_NAME = 'tgd-funnel-session-id';
|
|
6
|
+
const STICKY_SESSION_COOKIE_NAME = 'tgd-session-id';
|
|
6
7
|
const SESSION_MAX_AGE = 30 * 24 * 60 * 60; // 30 days
|
|
7
8
|
/**
|
|
8
9
|
* Save funnel session ID to browser cookie
|
|
@@ -37,3 +38,24 @@ export function clearFunnelSessionCookie() {
|
|
|
37
38
|
export function hasFunnelSessionCookie() {
|
|
38
39
|
return !!getFunnelSessionCookie();
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve funnel variant ID from sticky session (for A/B tracking)
|
|
43
|
+
* The variant ID is stored in the sticky session metadata by plugin-routing
|
|
44
|
+
*/
|
|
45
|
+
export function getFunnelVariantId() {
|
|
46
|
+
if (typeof document === 'undefined')
|
|
47
|
+
return undefined;
|
|
48
|
+
try {
|
|
49
|
+
const cookie = document.cookie
|
|
50
|
+
.split('; ')
|
|
51
|
+
.find((row) => row.startsWith(`${STICKY_SESSION_COOKIE_NAME}=`));
|
|
52
|
+
if (!cookie)
|
|
53
|
+
return undefined;
|
|
54
|
+
const sessionData = JSON.parse(decodeURIComponent(cookie.split('=')[1]));
|
|
55
|
+
return sessionData?.metadata?.funnelVariantId;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.warn('Failed to parse sticky session for variant ID:', error);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/v2/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export * from './core/isoData';
|
|
|
10
10
|
export * from './core/utils/currency';
|
|
11
11
|
export * from './core/utils/pluginConfig';
|
|
12
12
|
export * from './core/utils/products';
|
|
13
|
+
export * from './core/utils/previewMode';
|
|
14
|
+
export * from './core/utils/configHotReload';
|
|
13
15
|
export * from './core/pathRemapping';
|
|
14
16
|
export type { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutSessionPreview, Promotion } from './core/resources/checkout';
|
|
15
17
|
export type { Order, OrderLineItem } from './core/utils/order';
|
|
@@ -23,7 +25,7 @@ export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './co
|
|
|
23
25
|
export type { StoreConfig } from './core/resources/storeConfig';
|
|
24
26
|
export { FunnelActionType } from './core/resources/funnel';
|
|
25
27
|
export type { BackNavigationActionData, CartUpdatedActionData, DirectNavigationActionData, FormSubmitActionData, FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelAction as FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, NextAction, OfferAcceptedActionData, OfferDeclinedActionData, PaymentFailedActionData, PaymentSuccessActionData, SimpleFunnelContext } from './core/resources/funnel';
|
|
26
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
28
|
+
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
27
29
|
export type { DebugScript } from './react';
|
|
28
30
|
export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react/hooks/useTranslation';
|
|
29
31
|
export type { FunnelContextValue } from './react/hooks/useFunnel';
|
|
@@ -36,3 +38,4 @@ export type { UseCustomerInfosOptions, UseCustomerInfosResult } from './react/ho
|
|
|
36
38
|
export type { UseCustomerOrdersOptions, UseCustomerOrdersResult } from './react/hooks/useCustomerOrders';
|
|
37
39
|
export type { UseCustomerSubscriptionsOptions, UseCustomerSubscriptionsResult } from './react/hooks/useCustomerSubscriptions';
|
|
38
40
|
export type { UseShippingRatesQueryOptions, UseShippingRatesQueryResult } from './react/hooks/useShippingRatesQuery';
|
|
41
|
+
export type { PreviewOfferSummary, UsePreviewOfferOptions, UsePreviewOfferResult } from './react/hooks/usePreviewOffer';
|
package/dist/v2/index.js
CHANGED
|
@@ -11,8 +11,10 @@ export * from './core/isoData';
|
|
|
11
11
|
export * from './core/utils/currency';
|
|
12
12
|
export * from './core/utils/pluginConfig';
|
|
13
13
|
export * from './core/utils/products';
|
|
14
|
+
export * from './core/utils/previewMode';
|
|
15
|
+
export * from './core/utils/configHotReload';
|
|
14
16
|
// Path remapping helpers (framework-agnostic)
|
|
15
17
|
export * from './core/pathRemapping';
|
|
16
18
|
export { FunnelActionType } from './core/resources/funnel';
|
|
17
19
|
// React exports (hooks and components only, types are exported above)
|
|
18
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
20
|
+
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
@@ -15,9 +15,12 @@ import { useTagadaContext } from '../providers/TagadaProvider';
|
|
|
15
15
|
import { getGlobalApiClient } from './useApiQuery';
|
|
16
16
|
import { usePluginConfig } from './usePluginConfig';
|
|
17
17
|
export function useOfferQuery(options) {
|
|
18
|
-
const { offerId, enabled = true, mainOrderId } = options;
|
|
18
|
+
const { offerId, enabled = true, mainOrderId: rawMainOrderId } = options;
|
|
19
19
|
const { storeId } = usePluginConfig();
|
|
20
20
|
const { isSessionInitialized, session } = useTagadaContext();
|
|
21
|
+
// 🎯 Normalize mainOrderId: treat undefined and empty string as the same
|
|
22
|
+
// This prevents effect re-runs when value changes from undefined to ''
|
|
23
|
+
const mainOrderId = rawMainOrderId || undefined;
|
|
21
24
|
// ─────────────────────────────────────────────────────────────────────────
|
|
22
25
|
// State
|
|
23
26
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -140,17 +143,28 @@ export function useOfferQuery(options) {
|
|
|
140
143
|
});
|
|
141
144
|
}, [initCheckoutSessionAsync, session?.customerId, isSessionInitialized]);
|
|
142
145
|
// ─────────────────────────────────────────────────────────────────────────
|
|
143
|
-
// 2. Auto-initialize checkout session when
|
|
146
|
+
// 2. Auto-initialize checkout session when offer is loaded
|
|
147
|
+
// (mainOrderId is optional - used for post-purchase upsells, not needed for standalone offers)
|
|
144
148
|
// ─────────────────────────────────────────────────────────────────────────
|
|
145
149
|
useEffect(() => {
|
|
146
|
-
|
|
150
|
+
// Only require offer and session to be ready (mainOrderId is optional)
|
|
151
|
+
if (!offer || !isSessionInitialized) {
|
|
147
152
|
return;
|
|
148
|
-
|
|
153
|
+
}
|
|
154
|
+
if (checkoutSession.checkoutSessionId || isInitializing) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// 🎯 CRITICAL: Check and set initialization flag atomically
|
|
158
|
+
// This ensures only ONE effect can proceed with initialization
|
|
159
|
+
// Once set to true, it stays true (never reset) to prevent duplicate inits
|
|
160
|
+
if (hasInitializedRef.current) {
|
|
149
161
|
return;
|
|
162
|
+
}
|
|
163
|
+
hasInitializedRef.current = true;
|
|
150
164
|
const initSession = async () => {
|
|
151
|
-
hasInitializedRef.current = true;
|
|
152
165
|
setIsInitializing(true);
|
|
153
166
|
try {
|
|
167
|
+
// mainOrderId is optional - pass undefined if not provided
|
|
154
168
|
const result = await initCheckoutSession(offerId, mainOrderId);
|
|
155
169
|
setCheckoutSession(prev => ({
|
|
156
170
|
...prev,
|
|
@@ -160,7 +174,7 @@ export function useOfferQuery(options) {
|
|
|
160
174
|
await fetchOrderSummary(result.checkoutSessionId);
|
|
161
175
|
}
|
|
162
176
|
catch (err) {
|
|
163
|
-
console.error('Failed to init checkout session:', err);
|
|
177
|
+
console.error('[useOfferQuery] Failed to init checkout session:', err);
|
|
164
178
|
hasInitializedRef.current = false; // Allow retry on error
|
|
165
179
|
}
|
|
166
180
|
finally {
|
|
@@ -198,7 +212,8 @@ export function useOfferQuery(options) {
|
|
|
198
212
|
const product = variant?.product;
|
|
199
213
|
if (!variant || !product)
|
|
200
214
|
continue;
|
|
201
|
-
|
|
215
|
+
// Currency is determined from the checkout session, not from the offer
|
|
216
|
+
const currency = checkoutSession.orderSummary?.currency || 'USD';
|
|
202
217
|
const currencyOption = price?.currencyOptions?.[currency];
|
|
203
218
|
const unitAmount = currencyOption?.amount ?? variant.price ?? 0;
|
|
204
219
|
items.push({
|
|
@@ -282,21 +297,39 @@ export function useOfferQuery(options) {
|
|
|
282
297
|
const orderSummary = checkoutSession.orderSummary;
|
|
283
298
|
if (orderSummary?.options?.[productId]) {
|
|
284
299
|
const currency = orderSummary.currency || 'USD';
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
unitAmount
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
// Find the active line item for this product to get the correct priceId
|
|
301
|
+
const activeLineItem = orderSummary.items?.find((item) => item.productId === productId);
|
|
302
|
+
const activePriceId = activeLineItem?.priceId;
|
|
303
|
+
return orderSummary.options[productId].map((variant) => {
|
|
304
|
+
// Try to find the price that matches the active priceId, otherwise use first price
|
|
305
|
+
let unitAmount = 0;
|
|
306
|
+
if (activePriceId && variant.prices) {
|
|
307
|
+
const matchingPrice = variant.prices.find((p) => p.id === activePriceId);
|
|
308
|
+
if (matchingPrice?.currencyOptions?.[currency]?.amount) {
|
|
309
|
+
unitAmount = matchingPrice.currencyOptions[currency].amount;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Fallback to first price if no match found
|
|
313
|
+
if (unitAmount === 0 && variant.prices?.[0]?.currencyOptions?.[currency]?.amount) {
|
|
314
|
+
unitAmount = variant.prices[0].currencyOptions[currency].amount;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
variantId: variant.id,
|
|
318
|
+
variantName: variant.name,
|
|
319
|
+
sku: variant.sku || null,
|
|
320
|
+
imageUrl: variant.imageUrl || null,
|
|
321
|
+
unitAmount,
|
|
322
|
+
currency,
|
|
323
|
+
isDefault: variant.default ?? false,
|
|
324
|
+
};
|
|
325
|
+
});
|
|
294
326
|
}
|
|
295
327
|
// Fallback to static offer data
|
|
296
328
|
if (!offer?.offerLineItems)
|
|
297
329
|
return [];
|
|
298
330
|
const variants = [];
|
|
299
|
-
|
|
331
|
+
// Currency is determined from the checkout session, not from the offer
|
|
332
|
+
const currency = checkoutSession.orderSummary?.currency || 'USD';
|
|
300
333
|
for (const item of offer.offerLineItems) {
|
|
301
334
|
const price = item.price;
|
|
302
335
|
const variant = price?.variant;
|
|
@@ -28,10 +28,8 @@ export function usePaymentQuery() {
|
|
|
28
28
|
const { createSession, startChallenge } = useThreeds();
|
|
29
29
|
// Track challenge in progress to prevent multiple challenges
|
|
30
30
|
const challengeInProgressRef = useRef(false);
|
|
31
|
-
// Stabilize environment value to prevent re-renders
|
|
32
|
-
const currentEnvironment = useMemo(() => environment?.environment || 'local', [environment?.environment]);
|
|
33
31
|
// Get API key from embedded configuration with proper environment detection
|
|
34
|
-
const apiKey = useMemo(() => getBasisTheoryApiKey(
|
|
32
|
+
const apiKey = useMemo(() => getBasisTheoryApiKey(), []); // Auto-detects environment
|
|
35
33
|
// Initialize BasisTheory using React wrapper
|
|
36
34
|
const { bt: basisTheory, error: btError } = useBasisTheory(apiKey, {
|
|
37
35
|
elements: false,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePreviewOffer - Lightweight offer preview without checkout sessions
|
|
3
|
+
*
|
|
4
|
+
* This hook provides a fast, client-side offer preview by:
|
|
5
|
+
* 1. Fetching offer data with pre-computed summaries
|
|
6
|
+
* 2. Allowing variant/quantity selection
|
|
7
|
+
* 3. Recalculating totals on-the-fly without backend calls
|
|
8
|
+
* 4. NO checkout session creation - perfect for browsing/previewing
|
|
9
|
+
*/
|
|
10
|
+
import { Offer } from '../../core/resources/offers';
|
|
11
|
+
export interface PreviewLineItemSelection {
|
|
12
|
+
lineItemId: string;
|
|
13
|
+
variantId: string;
|
|
14
|
+
quantity: number;
|
|
15
|
+
priceId?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PreviewOfferSummary {
|
|
18
|
+
items: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
productId: string;
|
|
21
|
+
variantId: string;
|
|
22
|
+
priceId: string;
|
|
23
|
+
productName: string;
|
|
24
|
+
variantName: string;
|
|
25
|
+
imageUrl: string | null;
|
|
26
|
+
quantity: number;
|
|
27
|
+
unitAmount: number;
|
|
28
|
+
amount: number;
|
|
29
|
+
adjustedAmount: number;
|
|
30
|
+
currency: string;
|
|
31
|
+
}>;
|
|
32
|
+
totalAmount: number;
|
|
33
|
+
totalAdjustedAmount: number;
|
|
34
|
+
totalPromotionAmount: number;
|
|
35
|
+
currency: string;
|
|
36
|
+
options: Record<string, Array<{
|
|
37
|
+
variantId: string;
|
|
38
|
+
variantName: string;
|
|
39
|
+
sku: string | null;
|
|
40
|
+
imageUrl: string | null;
|
|
41
|
+
prices: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
currencyOptions: Record<string, {
|
|
44
|
+
amount: number;
|
|
45
|
+
currency: string;
|
|
46
|
+
}>;
|
|
47
|
+
}>;
|
|
48
|
+
default: boolean;
|
|
49
|
+
}>>;
|
|
50
|
+
}
|
|
51
|
+
export interface UsePreviewOfferOptions {
|
|
52
|
+
offerId: string;
|
|
53
|
+
currency?: string;
|
|
54
|
+
initialSelections?: Record<string, PreviewLineItemSelection>;
|
|
55
|
+
}
|
|
56
|
+
export interface UsePreviewOfferResult {
|
|
57
|
+
offer: Offer | null;
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
isFetching: boolean;
|
|
60
|
+
isPaying: boolean;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
summary: PreviewOfferSummary | null;
|
|
63
|
+
selections: Record<string, PreviewLineItemSelection>;
|
|
64
|
+
selectVariant: (lineItemId: string, variantId: string) => void;
|
|
65
|
+
updateQuantity: (lineItemId: string, quantity: number) => void;
|
|
66
|
+
selectVariantByProduct: (productId: string, variantId: string) => void;
|
|
67
|
+
updateQuantityByProduct: (productId: string, quantity: number) => void;
|
|
68
|
+
getAvailableVariants: (productId: string) => Array<{
|
|
69
|
+
variantId: string;
|
|
70
|
+
variantName: string;
|
|
71
|
+
imageUrl: string | null;
|
|
72
|
+
unitAmount: number;
|
|
73
|
+
currency: string;
|
|
74
|
+
}>;
|
|
75
|
+
pay: (mainOrderId?: string) => Promise<{
|
|
76
|
+
checkoutUrl: string;
|
|
77
|
+
}>;
|
|
78
|
+
toCheckout: (mainOrderId?: string) => Promise<{
|
|
79
|
+
checkoutSessionId?: string;
|
|
80
|
+
checkoutToken?: string;
|
|
81
|
+
checkoutUrl: string;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
export declare function usePreviewOffer(options: UsePreviewOfferOptions): UsePreviewOfferResult;
|