@tagadapay/plugin-sdk 3.1.2 → 3.1.8
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/README.md +1129 -1129
- package/build-cdn.js +113 -113
- package/dist/external-tracker.js +1104 -491
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/hooks/useApplePay.js +25 -36
- package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/react/utils/money.d.ts +4 -3
- package/dist/react/utils/money.js +39 -6
- package/dist/react/utils/trackingUtils.js +1 -0
- package/dist/v2/core/client.js +34 -2
- package/dist/v2/core/config/environment.js +9 -2
- package/dist/v2/core/funnelClient.d.ts +92 -1
- package/dist/v2/core/funnelClient.js +247 -3
- package/dist/v2/core/resources/apiClient.js +1 -1
- package/dist/v2/core/resources/checkout.d.ts +68 -0
- package/dist/v2/core/resources/funnel.d.ts +15 -0
- package/dist/v2/core/resources/payments.d.ts +50 -3
- package/dist/v2/core/resources/payments.js +38 -7
- package/dist/v2/core/utils/pluginConfig.js +40 -5
- package/dist/v2/core/utils/previewMode.d.ts +3 -0
- package/dist/v2/core/utils/previewMode.js +44 -14
- package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
- package/dist/v2/core/utils/previewModeIndicator.js +414 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
- package/dist/v2/core/utils/tokenStorage.js +15 -1
- package/dist/v2/index.d.ts +6 -1
- package/dist/v2/index.js +6 -1
- package/dist/v2/react/components/ApplePayButton.d.ts +21 -121
- package/dist/v2/react/components/ApplePayButton.js +221 -290
- package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
- package/dist/v2/react/components/FunnelScriptInjector.js +128 -24
- package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
- package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
- package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
- package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +42 -6
- package/dist/v2/react/hooks/useFunnel.js +25 -5
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +9 -3
- package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +32 -2
- package/dist/v2/react/hooks/usePaymentQuery.js +304 -7
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
- package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +62 -0
- package/dist/v2/react/hooks/useStepConfig.js +52 -0
- package/dist/v2/react/index.d.ts +9 -3
- package/dist/v2/react/index.js +5 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +27 -19
- package/dist/v2/react/providers/TagadaProvider.js +7 -7
- package/dist/v2/standalone/external-tracker.d.ts +2 -0
- package/dist/v2/standalone/external-tracker.js +6 -3
- package/package.json +112 -112
- package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
- package/dist/v2/react/hooks/useApplePay.js +0 -247
|
@@ -3,6 +3,7 @@ import { EventDispatcher } from './utils/eventDispatcher';
|
|
|
3
3
|
import { getFunnelSessionCookie, setFunnelSessionCookie } from './utils/sessionStorage';
|
|
4
4
|
import { detectEnvironment } from './config/environment';
|
|
5
5
|
import { getSDKParams } from './utils/previewMode';
|
|
6
|
+
import { injectPreviewModeIndicator } from './utils/previewModeIndicator';
|
|
6
7
|
/**
|
|
7
8
|
* Get the funnel ID from the injected HTML
|
|
8
9
|
* Returns undefined if not available
|
|
@@ -57,6 +58,216 @@ function getAssignedFunnelStep() {
|
|
|
57
58
|
}
|
|
58
59
|
return undefined;
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse step config from a string value (handles both JSON and URL-encoded formats)
|
|
63
|
+
* Robust parsing with multiple fallback strategies
|
|
64
|
+
*/
|
|
65
|
+
function parseStepConfig(value) {
|
|
66
|
+
if (!value || typeof value !== 'string')
|
|
67
|
+
return undefined;
|
|
68
|
+
const trimmed = value.trim();
|
|
69
|
+
if (!trimmed)
|
|
70
|
+
return undefined;
|
|
71
|
+
// Try parsing strategies in order of likelihood
|
|
72
|
+
const strategies = [
|
|
73
|
+
// Strategy 1: Direct JSON parse (for properly escaped window variable)
|
|
74
|
+
() => JSON.parse(trimmed),
|
|
75
|
+
// Strategy 2: URL decode then JSON parse (for meta tag)
|
|
76
|
+
() => JSON.parse(decodeURIComponent(trimmed)),
|
|
77
|
+
// Strategy 3: Double URL decode (edge case: double-encoded)
|
|
78
|
+
() => JSON.parse(decodeURIComponent(decodeURIComponent(trimmed))),
|
|
79
|
+
];
|
|
80
|
+
for (const strategy of strategies) {
|
|
81
|
+
try {
|
|
82
|
+
const result = strategy();
|
|
83
|
+
if (result && typeof result === 'object') {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Try next strategy
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// All strategies failed
|
|
92
|
+
if (typeof console !== 'undefined') {
|
|
93
|
+
console.warn('[SDK] Failed to parse stepConfig:', trimmed.substring(0, 100));
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Cache for local funnel config (loaded once)
|
|
98
|
+
let localFunnelConfigCache = undefined;
|
|
99
|
+
let localFunnelConfigLoading = false;
|
|
100
|
+
/**
|
|
101
|
+
* Check if we're in true local development (not CDN deployment)
|
|
102
|
+
*/
|
|
103
|
+
function isLocalDevelopment() {
|
|
104
|
+
if (typeof window === 'undefined')
|
|
105
|
+
return false;
|
|
106
|
+
const hostname = window.location.hostname;
|
|
107
|
+
// True local: localhost without CDN subdomain pattern
|
|
108
|
+
return hostname === 'localhost' ||
|
|
109
|
+
hostname === '127.0.0.1' ||
|
|
110
|
+
(hostname.endsWith('.localhost') && !hostname.includes('.cdn.'));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Load local funnel config from /config/funnel.local.json (for local dev only)
|
|
114
|
+
* This replaces the old resources.static.json with a more complete structure
|
|
115
|
+
*
|
|
116
|
+
* Example funnel.local.json:
|
|
117
|
+
* {
|
|
118
|
+
* "funnelId": "funnelv2_xxx",
|
|
119
|
+
* "stepId": "step_checkout",
|
|
120
|
+
* "staticResources": {
|
|
121
|
+
* "offer": "offer_xxx",
|
|
122
|
+
* "product": "product_xxx"
|
|
123
|
+
* },
|
|
124
|
+
* "paymentFlowId": "flow_xxx"
|
|
125
|
+
* }
|
|
126
|
+
*/
|
|
127
|
+
export async function loadLocalFunnelConfig() {
|
|
128
|
+
// Only in true local development
|
|
129
|
+
if (!isLocalDevelopment())
|
|
130
|
+
return null;
|
|
131
|
+
// Return cached value if already loaded
|
|
132
|
+
if (localFunnelConfigCache !== undefined) {
|
|
133
|
+
return localFunnelConfigCache;
|
|
134
|
+
}
|
|
135
|
+
// Prevent concurrent loads
|
|
136
|
+
if (localFunnelConfigLoading) {
|
|
137
|
+
// Wait for existing load
|
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
139
|
+
return localFunnelConfigCache ?? null;
|
|
140
|
+
}
|
|
141
|
+
localFunnelConfigLoading = true;
|
|
142
|
+
try {
|
|
143
|
+
console.log('🛠️ [SDK] Loading local funnel config from /config/funnel.local.json...');
|
|
144
|
+
const response = await fetch('/config/funnel.local.json');
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
console.log('🛠️ [SDK] funnel.local.json not found (this is fine in production)');
|
|
147
|
+
localFunnelConfigCache = null;
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const config = await response.json();
|
|
151
|
+
console.log('🛠️ [SDK] ✅ Loaded local funnel config:', config);
|
|
152
|
+
localFunnelConfigCache = config;
|
|
153
|
+
return config;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.log('🛠️ [SDK] funnel.local.json not available:', error);
|
|
157
|
+
localFunnelConfigCache = null;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
localFunnelConfigLoading = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the cached local funnel config (sync access after loadLocalFunnelConfig)
|
|
166
|
+
*/
|
|
167
|
+
export function getLocalFunnelConfig() {
|
|
168
|
+
return localFunnelConfigCache ?? null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Convert LocalFunnelConfig to RuntimeStepConfig
|
|
172
|
+
*/
|
|
173
|
+
function localConfigToStepConfig(local) {
|
|
174
|
+
return {
|
|
175
|
+
payment: local.paymentFlowId ? { paymentFlowId: local.paymentFlowId } : undefined,
|
|
176
|
+
staticResources: local.staticResources,
|
|
177
|
+
scripts: local.scripts,
|
|
178
|
+
pixels: local.pixels,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the runtime step configuration
|
|
183
|
+
* Contains payment flow, static resources, scripts, and pixel tracking
|
|
184
|
+
*
|
|
185
|
+
* Priority:
|
|
186
|
+
* 1. Local funnel config (local dev only - /config/funnel.local.json) - HIGHEST in local dev
|
|
187
|
+
* 2. Window variable (production - HTML injection)
|
|
188
|
+
* 3. Meta tag (production - HTML injection fallback)
|
|
189
|
+
*
|
|
190
|
+
* This allows local developers to override injected config for testing.
|
|
191
|
+
*
|
|
192
|
+
* Returns undefined if not available
|
|
193
|
+
*/
|
|
194
|
+
export function getAssignedStepConfig() {
|
|
195
|
+
if (typeof window === 'undefined')
|
|
196
|
+
return undefined;
|
|
197
|
+
// Method 1: Local dev override (HIGHEST PRIORITY in local dev)
|
|
198
|
+
// Allows developers to test different configurations without redeploying
|
|
199
|
+
const localConfig = getLocalFunnelConfig();
|
|
200
|
+
if (localConfig) {
|
|
201
|
+
console.log('🛠️ [SDK] Using local funnel.local.json (overrides injected)');
|
|
202
|
+
return localConfigToStepConfig(localConfig);
|
|
203
|
+
}
|
|
204
|
+
// Method 2: Window variable (production - HTML injection)
|
|
205
|
+
const windowValue = window.__TGD_STEP_CONFIG__;
|
|
206
|
+
if (windowValue) {
|
|
207
|
+
const parsed = parseStepConfig(windowValue);
|
|
208
|
+
if (parsed)
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
// Method 3: Meta tag fallback (URL-encoded)
|
|
212
|
+
if (typeof document !== 'undefined') {
|
|
213
|
+
const meta = document.querySelector('meta[name="x-step-config"]');
|
|
214
|
+
const content = meta?.getAttribute('content');
|
|
215
|
+
if (content) {
|
|
216
|
+
const parsed = parseStepConfig(content);
|
|
217
|
+
if (parsed)
|
|
218
|
+
return parsed;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get the assigned payment flow ID from step config or legacy injection
|
|
225
|
+
* Returns undefined if not available
|
|
226
|
+
*/
|
|
227
|
+
export function getAssignedPaymentFlowId() {
|
|
228
|
+
// Method 1: New stepConfig (preferred)
|
|
229
|
+
const stepConfig = getAssignedStepConfig();
|
|
230
|
+
if (stepConfig?.payment?.paymentFlowId) {
|
|
231
|
+
return stepConfig.payment.paymentFlowId;
|
|
232
|
+
}
|
|
233
|
+
// Method 2: Legacy direct injection (backward compatibility)
|
|
234
|
+
if (typeof window !== 'undefined') {
|
|
235
|
+
// Legacy window variable
|
|
236
|
+
if (window.__TGD_PAYMENT_FLOW_ID__) {
|
|
237
|
+
return window.__TGD_PAYMENT_FLOW_ID__;
|
|
238
|
+
}
|
|
239
|
+
// Legacy meta tag
|
|
240
|
+
if (typeof document !== 'undefined') {
|
|
241
|
+
const meta = document.querySelector('meta[name="x-payment-flow-id"]');
|
|
242
|
+
return meta?.getAttribute('content') || undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get the assigned static resources from step config
|
|
249
|
+
* Returns undefined if not available
|
|
250
|
+
*/
|
|
251
|
+
export function getAssignedStaticResources() {
|
|
252
|
+
const stepConfig = getAssignedStepConfig();
|
|
253
|
+
return stepConfig?.staticResources;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get the assigned scripts from step config
|
|
257
|
+
* Returns only enabled scripts, filtered by position if specified
|
|
258
|
+
*/
|
|
259
|
+
export function getAssignedScripts(position) {
|
|
260
|
+
const stepConfig = getAssignedStepConfig();
|
|
261
|
+
if (!stepConfig?.scripts)
|
|
262
|
+
return undefined;
|
|
263
|
+
// Filter enabled scripts
|
|
264
|
+
let scripts = stepConfig.scripts.filter(s => s.enabled);
|
|
265
|
+
// Filter by position if specified
|
|
266
|
+
if (position) {
|
|
267
|
+
scripts = scripts.filter(s => s.position === position || (!s.position && position === 'head-end'));
|
|
268
|
+
}
|
|
269
|
+
return scripts.length > 0 ? scripts : undefined;
|
|
270
|
+
}
|
|
60
271
|
export class FunnelClient {
|
|
61
272
|
constructor(config) {
|
|
62
273
|
this.eventDispatcher = new EventDispatcher();
|
|
@@ -152,6 +363,9 @@ export class FunnelClient {
|
|
|
152
363
|
// Priority: config override > injected > URL/prop
|
|
153
364
|
const finalFunnelId = this.config.funnelId || injectedFunnelId || effectiveFunnelId;
|
|
154
365
|
const finalStepId = this.config.stepId || injectedStepId;
|
|
366
|
+
// 🎯 Determine funnelEnv from URL params
|
|
367
|
+
const urlParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null;
|
|
368
|
+
const funnelEnv = urlParams?.get('funnelEnv');
|
|
155
369
|
if (this.config.debugMode) {
|
|
156
370
|
console.log('🚀 [FunnelClient] Auto-initializing...', {
|
|
157
371
|
existingSessionId,
|
|
@@ -160,6 +374,9 @@ export class FunnelClient {
|
|
|
160
374
|
funnelStepId: finalStepId, // 🎯 Log step ID for debugging
|
|
161
375
|
draft: sdkParams.draft, // 🎯 Log draft mode
|
|
162
376
|
funnelTracking: sdkParams.funnelTracking, // 🎯 Log tracking flag
|
|
377
|
+
funnelEnv, // 🎯 Log funnel environment
|
|
378
|
+
tagadaClientEnv: sdkParams.tagadaClientEnv, // 🎯 Log client environment
|
|
379
|
+
tagadaClientBaseUrl: sdkParams.tagadaClientBaseUrl, // 🎯 Log custom API URL
|
|
163
380
|
source: {
|
|
164
381
|
funnelId: this.config.funnelId ? 'config' : injectedFunnelId ? 'injected' : effectiveFunnelId ? 'url/prop' : 'none',
|
|
165
382
|
stepId: this.config.stepId ? 'config' : injectedStepId ? 'injected' : 'none',
|
|
@@ -181,10 +398,15 @@ export class FunnelClient {
|
|
|
181
398
|
funnelStepId: finalStepId, // 🎯 Pass step ID to backend (with config override)
|
|
182
399
|
draft: sdkParams.draft, // 🎯 Pass draft mode explicitly (more robust than URL parsing)
|
|
183
400
|
funnelTracking: sdkParams.funnelTracking, // 🎯 Pass funnel tracking flag explicitly
|
|
401
|
+
funnelEnv: funnelEnv || undefined, // 🎯 Pass funnel environment (staging/production)
|
|
402
|
+
tagadaClientEnv: sdkParams.tagadaClientEnv, // 🎯 Pass client environment override
|
|
403
|
+
tagadaClientBaseUrl: sdkParams.tagadaClientBaseUrl, // 🎯 Pass custom API base URL
|
|
184
404
|
});
|
|
185
405
|
if (response.success && response.context) {
|
|
186
406
|
const enriched = this.enrichContext(response.context);
|
|
187
407
|
this.handleSessionSuccess(enriched);
|
|
408
|
+
// 🔍 Auto-inject preview mode indicator if in preview/dev mode
|
|
409
|
+
injectPreviewModeIndicator();
|
|
188
410
|
return enriched;
|
|
189
411
|
}
|
|
190
412
|
else {
|
|
@@ -234,6 +456,8 @@ export class FunnelClient {
|
|
|
234
456
|
if (response.success && response.context) {
|
|
235
457
|
const enriched = this.enrichContext(response.context);
|
|
236
458
|
this.handleSessionSuccess(enriched);
|
|
459
|
+
// 🔍 Auto-inject preview mode indicator if in preview/dev mode
|
|
460
|
+
injectPreviewModeIndicator();
|
|
237
461
|
return enriched;
|
|
238
462
|
}
|
|
239
463
|
else {
|
|
@@ -253,8 +477,23 @@ export class FunnelClient {
|
|
|
253
477
|
* @param options.fireAndForget - If true, queues navigation to QStash and returns immediately without waiting for result
|
|
254
478
|
* @param options.customerTags - Customer tags to set (merged with existing customer tags)
|
|
255
479
|
* @param options.deviceId - Device ID for geo/device tag enrichment (optional, rarely needed)
|
|
480
|
+
* @param options.autoRedirect - Override global autoRedirect setting for this specific call (default: use config)
|
|
256
481
|
*/
|
|
257
482
|
async navigate(event, options) {
|
|
483
|
+
// Wait for session if requested
|
|
484
|
+
if (options?.waitForSession && !this.state.context?.sessionId) {
|
|
485
|
+
if (this.config.debugMode) {
|
|
486
|
+
console.log('⏳ [FunnelClient] Waiting for session before navigation...');
|
|
487
|
+
}
|
|
488
|
+
const maxWaitTime = 5000;
|
|
489
|
+
const startTime = Date.now();
|
|
490
|
+
while (!this.state.context?.sessionId && (Date.now() - startTime < maxWaitTime)) {
|
|
491
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
492
|
+
}
|
|
493
|
+
if (this.state.context?.sessionId && this.config.debugMode) {
|
|
494
|
+
console.log('✅ [FunnelClient] Session ready, proceeding with navigation');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
258
497
|
if (!this.state.context?.sessionId)
|
|
259
498
|
throw new Error('No active session');
|
|
260
499
|
this.updateState({ isNavigating: true, isLoading: true });
|
|
@@ -324,7 +563,10 @@ export class FunnelClient {
|
|
|
324
563
|
return result;
|
|
325
564
|
}
|
|
326
565
|
// Normal navigation: handle redirect
|
|
327
|
-
|
|
566
|
+
// Per-call option takes precedence over global config
|
|
567
|
+
const shouldAutoRedirect = options?.autoRedirect !== undefined
|
|
568
|
+
? options.autoRedirect
|
|
569
|
+
: this.config.autoRedirect !== false; // Default to true
|
|
328
570
|
// Skip refreshSession if auto-redirecting (next page will initialize with fresh state)
|
|
329
571
|
// Only refresh if staying on same page (autoRedirect: false)
|
|
330
572
|
if (!shouldAutoRedirect) {
|
|
@@ -351,12 +593,14 @@ export class FunnelClient {
|
|
|
351
593
|
}
|
|
352
594
|
/**
|
|
353
595
|
* Go to a specific step (direct navigation)
|
|
596
|
+
* @param stepId - Target step ID
|
|
597
|
+
* @param options - Navigation options (autoRedirect, etc.)
|
|
354
598
|
*/
|
|
355
|
-
async goToStep(stepId) {
|
|
599
|
+
async goToStep(stepId, options) {
|
|
356
600
|
return this.navigate({
|
|
357
601
|
type: FunnelActionType.DIRECT_NAVIGATION,
|
|
358
602
|
data: { targetStepId: stepId },
|
|
359
|
-
});
|
|
603
|
+
}, options);
|
|
360
604
|
}
|
|
361
605
|
/**
|
|
362
606
|
* Refresh session data
|
|
@@ -13,7 +13,7 @@ export class ApiClient {
|
|
|
13
13
|
this.MAX_REQUESTS = 30; // Max 30 requests per endpoint in window
|
|
14
14
|
this.axios = axios.create({
|
|
15
15
|
baseURL: config.baseURL,
|
|
16
|
-
timeout: config.timeout ||
|
|
16
|
+
timeout: config.timeout || 60000, // 60 seconds for payment operations
|
|
17
17
|
headers: {
|
|
18
18
|
'Content-Type': 'application/json',
|
|
19
19
|
...config.headers,
|
|
@@ -26,11 +26,79 @@ export interface CheckoutInitParams {
|
|
|
26
26
|
locale?: string;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
export interface UpsellTrigger {
|
|
30
|
+
id: string;
|
|
31
|
+
type: string;
|
|
32
|
+
productId: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface OrderBumpOfferVariant {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
sku: string;
|
|
38
|
+
imageUrl: string;
|
|
39
|
+
active: boolean;
|
|
40
|
+
default: boolean;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
externalVariantId: string | null;
|
|
44
|
+
description: string;
|
|
45
|
+
currency: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface OrderBumpOfferProduct {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
}
|
|
51
|
+
export interface PriceCurrencyOption {
|
|
52
|
+
rate: number;
|
|
53
|
+
amount: number;
|
|
54
|
+
lock: boolean;
|
|
55
|
+
date: string;
|
|
56
|
+
}
|
|
57
|
+
export interface OrderBumpOfferPrice {
|
|
58
|
+
id: string;
|
|
59
|
+
currencyOptions: Record<string, PriceCurrencyOption>;
|
|
60
|
+
}
|
|
61
|
+
export interface CheckoutOrderBumpOffer {
|
|
62
|
+
id: string;
|
|
63
|
+
type: string;
|
|
64
|
+
productId: string;
|
|
65
|
+
variantId: string;
|
|
66
|
+
priceId: string;
|
|
67
|
+
titleTrans: Record<string, string>;
|
|
68
|
+
descriptionTrans: Record<string, string>;
|
|
69
|
+
overrideImageUrl: string | null;
|
|
70
|
+
precheck: boolean;
|
|
71
|
+
displayPrice: boolean;
|
|
72
|
+
displayCompareAtPrice: boolean;
|
|
73
|
+
compareAtPriceDiscount: number;
|
|
74
|
+
quantity: number;
|
|
75
|
+
variant: OrderBumpOfferVariant;
|
|
76
|
+
product: OrderBumpOfferProduct;
|
|
77
|
+
price: OrderBumpOfferPrice;
|
|
78
|
+
}
|
|
79
|
+
export interface Upsell {
|
|
80
|
+
id: string;
|
|
81
|
+
type: string;
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
triggers: UpsellTrigger[];
|
|
84
|
+
orderBumpOffers: CheckoutOrderBumpOffer[];
|
|
85
|
+
}
|
|
86
|
+
export interface Store {
|
|
87
|
+
id: string;
|
|
88
|
+
accountId: string;
|
|
89
|
+
name: string;
|
|
90
|
+
presentmentCurrencies: string[];
|
|
91
|
+
chargeCurrencies: string[];
|
|
92
|
+
upsells: Upsell[];
|
|
93
|
+
emailDomains: string[];
|
|
94
|
+
integrations: any[];
|
|
95
|
+
}
|
|
29
96
|
export interface CheckoutSession {
|
|
30
97
|
id: string;
|
|
31
98
|
checkoutToken: string;
|
|
32
99
|
status: string;
|
|
33
100
|
storeId: string;
|
|
101
|
+
store: Store;
|
|
34
102
|
accountId: string;
|
|
35
103
|
customerId: string;
|
|
36
104
|
draft: boolean;
|
|
@@ -470,6 +470,21 @@ export interface FunnelInitializeRequest {
|
|
|
470
470
|
* When false, disables all funnel tracking events (useful for iframed previews in config editor)
|
|
471
471
|
*/
|
|
472
472
|
funnelTracking?: boolean;
|
|
473
|
+
/**
|
|
474
|
+
* 🎯 Funnel environment override ('staging' | 'production')
|
|
475
|
+
* Determines which funnel config to use
|
|
476
|
+
*/
|
|
477
|
+
funnelEnv?: 'staging' | 'production';
|
|
478
|
+
/**
|
|
479
|
+
* 🎯 Tagada client environment override ('production' | 'development' | 'local')
|
|
480
|
+
* Forces specific API environment
|
|
481
|
+
*/
|
|
482
|
+
tagadaClientEnv?: 'production' | 'development' | 'local';
|
|
483
|
+
/**
|
|
484
|
+
* 🎯 Tagada client base URL override
|
|
485
|
+
* Forces custom API base URL (e.g., for local development with ngrok)
|
|
486
|
+
*/
|
|
487
|
+
tagadaClientBaseUrl?: string;
|
|
473
488
|
}
|
|
474
489
|
export interface FunnelInitializeResponse {
|
|
475
490
|
success: boolean;
|
|
@@ -7,14 +7,14 @@ export interface Payment {
|
|
|
7
7
|
id: string;
|
|
8
8
|
status: string;
|
|
9
9
|
subStatus: string;
|
|
10
|
-
requireAction: 'none' | 'redirect' | 'error';
|
|
10
|
+
requireAction: 'none' | 'redirect' | 'error' | 'radar';
|
|
11
11
|
requireActionData?: {
|
|
12
|
-
type: 'redirect' | 'threeds_auth' | 'processor_auth' | 'error';
|
|
12
|
+
type: 'redirect' | 'threeds_auth' | 'processor_auth' | 'error' | 'stripe_radar' | 'finix_radar';
|
|
13
13
|
url?: string;
|
|
14
14
|
processed: boolean;
|
|
15
15
|
processorId?: string;
|
|
16
16
|
metadata?: {
|
|
17
|
-
type: 'redirect';
|
|
17
|
+
type: 'redirect' | 'stripe_radar' | 'finix_radar';
|
|
18
18
|
redirect?: {
|
|
19
19
|
redirectUrl: string;
|
|
20
20
|
returnUrl: string;
|
|
@@ -25,6 +25,12 @@ export interface Payment {
|
|
|
25
25
|
acsTransID: string;
|
|
26
26
|
messageVersion: string;
|
|
27
27
|
};
|
|
28
|
+
radar?: {
|
|
29
|
+
merchantId?: string;
|
|
30
|
+
environment?: 'sandbox' | 'live';
|
|
31
|
+
orderId?: string;
|
|
32
|
+
publishableKey?: string;
|
|
33
|
+
};
|
|
28
34
|
};
|
|
29
35
|
redirectUrl?: string;
|
|
30
36
|
resumeToken?: string;
|
|
@@ -76,6 +82,12 @@ export interface PaymentOptions {
|
|
|
76
82
|
threedsProvider?: 'basis_theory';
|
|
77
83
|
initiatedBy?: 'customer' | 'merchant';
|
|
78
84
|
source?: 'upsell' | 'checkout' | 'offer' | 'missing_club' | 'forced';
|
|
85
|
+
/**
|
|
86
|
+
* Override the store's default payment flow
|
|
87
|
+
* If not provided, the SDK will auto-read from stepConfig.paymentFlowId (if available)
|
|
88
|
+
* If neither is set, the store's selectedPaymentFlowId will be used
|
|
89
|
+
*/
|
|
90
|
+
paymentFlowId?: string;
|
|
79
91
|
/** @deprecated Use onPaymentSuccess instead - this will be removed in v3 */
|
|
80
92
|
onSuccess?: (response: PaymentResponse) => void;
|
|
81
93
|
/** @deprecated Use onPaymentFailed instead - this will be removed in v3 */
|
|
@@ -147,6 +159,7 @@ export declare class PaymentsResource {
|
|
|
147
159
|
processPaymentDirect(checkoutSessionId: string, paymentInstrumentId: string, threedsSessionId?: string, options?: {
|
|
148
160
|
initiatedBy?: 'customer' | 'merchant';
|
|
149
161
|
source?: 'upsell' | 'checkout' | 'offer' | 'missing_club' | 'forced';
|
|
162
|
+
paymentFlowId?: string;
|
|
150
163
|
}): Promise<PaymentResponse>;
|
|
151
164
|
/**
|
|
152
165
|
* Get card payment instruments for customer
|
|
@@ -160,4 +173,38 @@ export declare class PaymentsResource {
|
|
|
160
173
|
* Get payment status
|
|
161
174
|
*/
|
|
162
175
|
getPaymentStatus(paymentId: string): Promise<Payment>;
|
|
176
|
+
/**
|
|
177
|
+
* Retrieve payment status from processor
|
|
178
|
+
* Used for external payment flows that require server-side status checks
|
|
179
|
+
*/
|
|
180
|
+
retrievePayment(paymentId: string): Promise<{
|
|
181
|
+
retrieveResult?: {
|
|
182
|
+
status?: string;
|
|
183
|
+
message?: string;
|
|
184
|
+
success?: boolean;
|
|
185
|
+
};
|
|
186
|
+
paymentId: string;
|
|
187
|
+
transactionCreated?: boolean;
|
|
188
|
+
status?: string;
|
|
189
|
+
transactionId?: string;
|
|
190
|
+
message?: string;
|
|
191
|
+
success?: boolean;
|
|
192
|
+
error?: any;
|
|
193
|
+
}>;
|
|
194
|
+
saveRadarSession(data: {
|
|
195
|
+
orderId?: string;
|
|
196
|
+
checkoutSessionId?: string;
|
|
197
|
+
finixRadarSessionId?: string;
|
|
198
|
+
finixRadarSessionData?: Record<string, unknown>;
|
|
199
|
+
stripeRadarSessionId?: string;
|
|
200
|
+
stripeRadarSessionData?: Record<string, unknown>;
|
|
201
|
+
}): Promise<{
|
|
202
|
+
success: boolean;
|
|
203
|
+
radarSessionId: string;
|
|
204
|
+
}>;
|
|
205
|
+
/**
|
|
206
|
+
* Complete payment after an action (3DS, radar, etc.)
|
|
207
|
+
* This resumes the payment flow that was paused for additional authentication/verification
|
|
208
|
+
*/
|
|
209
|
+
completePaymentAfterAction(paymentId: string): Promise<Payment>;
|
|
163
210
|
}
|
|
@@ -95,13 +95,23 @@ export class PaymentsResource {
|
|
|
95
95
|
* Process payment directly with checkout session
|
|
96
96
|
*/
|
|
97
97
|
async processPaymentDirect(checkoutSessionId, paymentInstrumentId, threedsSessionId, options = {}) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
console.log('[PaymentsResource] processPaymentDirect START', options.paymentFlowId ? `(flow: ${options.paymentFlowId})` : '');
|
|
99
|
+
try {
|
|
100
|
+
const response = await this.apiClient.post('/api/public/v1/checkout/pay-v2', {
|
|
101
|
+
checkoutSessionId,
|
|
102
|
+
paymentInstrumentId,
|
|
103
|
+
...(threedsSessionId && { threedsSessionId }),
|
|
104
|
+
...(options.initiatedBy && { initiatedBy: options.initiatedBy }),
|
|
105
|
+
...(options.source && { source: options.source }),
|
|
106
|
+
...(options.paymentFlowId && { paymentFlowId: options.paymentFlowId }),
|
|
107
|
+
});
|
|
108
|
+
console.log('[PaymentsResource] processPaymentDirect SUCCESS:', response);
|
|
109
|
+
return response;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('[PaymentsResource] processPaymentDirect ERROR:', error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
105
115
|
}
|
|
106
116
|
/**
|
|
107
117
|
* Get card payment instruments for customer
|
|
@@ -123,4 +133,25 @@ export class PaymentsResource {
|
|
|
123
133
|
async getPaymentStatus(paymentId) {
|
|
124
134
|
return this.apiClient.get(`/api/v1/payments/${paymentId}`);
|
|
125
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Retrieve payment status from processor
|
|
138
|
+
* Used for external payment flows that require server-side status checks
|
|
139
|
+
*/
|
|
140
|
+
async retrievePayment(paymentId) {
|
|
141
|
+
return this.apiClient.post('/api/v1/payments/retrieve', {
|
|
142
|
+
paymentId,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async saveRadarSession(data) {
|
|
146
|
+
return this.apiClient.post('/api/v1/radar-sessions', data);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Complete payment after an action (3DS, radar, etc.)
|
|
150
|
+
* This resumes the payment flow that was paused for additional authentication/verification
|
|
151
|
+
*/
|
|
152
|
+
async completePaymentAfterAction(paymentId) {
|
|
153
|
+
return this.apiClient.post('/api/v1/payments/complete-after-3ds', {
|
|
154
|
+
paymentId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
126
157
|
}
|
|
@@ -85,7 +85,10 @@ const loadLocalDevConfig = async (configVariant = 'default') => {
|
|
|
85
85
|
};
|
|
86
86
|
/**
|
|
87
87
|
* Load static resources for local development
|
|
88
|
-
*
|
|
88
|
+
*
|
|
89
|
+
* Priority:
|
|
90
|
+
* 1. /config/funnel.local.json (NEW - recommended)
|
|
91
|
+
* 2. /config/resources.static.json (OLD - deprecated, for backward compatibility)
|
|
89
92
|
*/
|
|
90
93
|
const loadStaticResources = async () => {
|
|
91
94
|
try {
|
|
@@ -99,15 +102,47 @@ const loadStaticResources = async () => {
|
|
|
99
102
|
if (!isLocalEnvironment(true)) {
|
|
100
103
|
return null;
|
|
101
104
|
}
|
|
102
|
-
//
|
|
103
|
-
|
|
105
|
+
// ============================================================
|
|
106
|
+
// PRIORITY 1: NEW format - funnel.local.json
|
|
107
|
+
// Also populates the stepConfig cache for getAssignedStepConfig()
|
|
108
|
+
// ============================================================
|
|
109
|
+
try {
|
|
110
|
+
console.log('🛠️ [V2] Attempting to load /config/funnel.local.json...');
|
|
111
|
+
const funnelResponse = await fetch('/config/funnel.local.json');
|
|
112
|
+
if (funnelResponse.ok) {
|
|
113
|
+
const funnelConfig = await funnelResponse.json();
|
|
114
|
+
console.log('🛠️ [V2] ✅ Loaded local funnel config (NEW format):', funnelConfig);
|
|
115
|
+
// Also trigger loadLocalFunnelConfig to populate the cache
|
|
116
|
+
// Import dynamically to avoid circular dependency
|
|
117
|
+
const { loadLocalFunnelConfig } = await import('../funnelClient');
|
|
118
|
+
await loadLocalFunnelConfig();
|
|
119
|
+
// Return staticResources in the old format for backward compatibility
|
|
120
|
+
// The new stepConfig system reads from funnel.local.json via getAssignedStepConfig()
|
|
121
|
+
if (funnelConfig.staticResources) {
|
|
122
|
+
// Transform flat format to old nested format for legacy compatibility
|
|
123
|
+
const transformed = {};
|
|
124
|
+
for (const [key, value] of Object.entries(funnelConfig.staticResources)) {
|
|
125
|
+
transformed[key] = { id: value };
|
|
126
|
+
}
|
|
127
|
+
return transformed;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
console.log('🛠️ [V2] funnel.local.json not found, trying legacy format...');
|
|
134
|
+
}
|
|
135
|
+
// ============================================================
|
|
136
|
+
// PRIORITY 2: OLD format - resources.static.json (deprecated)
|
|
137
|
+
// ============================================================
|
|
138
|
+
console.log('🛠️ [V2] Attempting to load /config/resources.static.json (legacy)...');
|
|
104
139
|
const response = await fetch('/config/resources.static.json');
|
|
105
140
|
if (!response.ok) {
|
|
106
|
-
console.log('🛠️ [V2]
|
|
141
|
+
console.log('🛠️ [V2] No local static resources found');
|
|
107
142
|
return null;
|
|
108
143
|
}
|
|
109
144
|
const staticResources = await response.json();
|
|
110
|
-
console.log('🛠️ [V2] ✅ Loaded
|
|
145
|
+
console.log('🛠️ [V2] ✅ Loaded legacy static resources:', staticResources);
|
|
111
146
|
return staticResources;
|
|
112
147
|
}
|
|
113
148
|
catch (error) {
|
|
@@ -42,6 +42,8 @@ export interface SDKOverrideParams {
|
|
|
42
42
|
draft?: boolean;
|
|
43
43
|
/** Enable/disable funnel tracking events (false in config editor iframe, true in funnel previews) */
|
|
44
44
|
funnelTracking?: boolean;
|
|
45
|
+
/** Funnel environment (staging/production) - determines which funnel config to use */
|
|
46
|
+
funnelEnv?: 'staging' | 'production';
|
|
45
47
|
/** Force specific environment (production/development/local) - overrides auto-detection */
|
|
46
48
|
tagadaClientEnv?: 'production' | 'development' | 'local';
|
|
47
49
|
/** Force custom API base URL - overrides environment-based URL */
|
|
@@ -68,6 +70,7 @@ export declare function getPreviewParams(): SDKOverrideParams;
|
|
|
68
70
|
export declare function isDraftMode(): boolean;
|
|
69
71
|
/**
|
|
70
72
|
* Set draft mode in storage for persistence
|
|
73
|
+
* ⚠️ ONLY writes to localStorage (not cookies) to avoid resurrection issues
|
|
71
74
|
*/
|
|
72
75
|
export declare function setDraftMode(draft: boolean): void;
|
|
73
76
|
/**
|