@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.
Files changed (56) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +113 -113
  3. package/dist/external-tracker.js +1104 -491
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/hooks/useApplePay.js +25 -36
  7. package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
  8. package/dist/react/providers/TagadaProvider.js +5 -5
  9. package/dist/react/utils/money.d.ts +4 -3
  10. package/dist/react/utils/money.js +39 -6
  11. package/dist/react/utils/trackingUtils.js +1 -0
  12. package/dist/v2/core/client.js +34 -2
  13. package/dist/v2/core/config/environment.js +9 -2
  14. package/dist/v2/core/funnelClient.d.ts +92 -1
  15. package/dist/v2/core/funnelClient.js +247 -3
  16. package/dist/v2/core/resources/apiClient.js +1 -1
  17. package/dist/v2/core/resources/checkout.d.ts +68 -0
  18. package/dist/v2/core/resources/funnel.d.ts +15 -0
  19. package/dist/v2/core/resources/payments.d.ts +50 -3
  20. package/dist/v2/core/resources/payments.js +38 -7
  21. package/dist/v2/core/utils/pluginConfig.js +40 -5
  22. package/dist/v2/core/utils/previewMode.d.ts +3 -0
  23. package/dist/v2/core/utils/previewMode.js +44 -14
  24. package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
  25. package/dist/v2/core/utils/previewModeIndicator.js +414 -0
  26. package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
  27. package/dist/v2/core/utils/tokenStorage.js +15 -1
  28. package/dist/v2/index.d.ts +6 -1
  29. package/dist/v2/index.js +6 -1
  30. package/dist/v2/react/components/ApplePayButton.d.ts +21 -121
  31. package/dist/v2/react/components/ApplePayButton.js +221 -290
  32. package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
  33. package/dist/v2/react/components/FunnelScriptInjector.js +128 -24
  34. package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
  35. package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
  36. package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
  37. package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
  38. package/dist/v2/react/hooks/useFunnel.d.ts +42 -6
  39. package/dist/v2/react/hooks/useFunnel.js +25 -5
  40. package/dist/v2/react/hooks/usePaymentPolling.d.ts +9 -3
  41. package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
  42. package/dist/v2/react/hooks/usePaymentQuery.d.ts +32 -2
  43. package/dist/v2/react/hooks/usePaymentQuery.js +304 -7
  44. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
  45. package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
  46. package/dist/v2/react/hooks/useStepConfig.d.ts +62 -0
  47. package/dist/v2/react/hooks/useStepConfig.js +52 -0
  48. package/dist/v2/react/index.d.ts +9 -3
  49. package/dist/v2/react/index.js +5 -1
  50. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +27 -19
  51. package/dist/v2/react/providers/TagadaProvider.js +7 -7
  52. package/dist/v2/standalone/external-tracker.d.ts +2 -0
  53. package/dist/v2/standalone/external-tracker.js +6 -3
  54. package/package.json +112 -112
  55. package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
  56. 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
- const shouldAutoRedirect = this.config.autoRedirect !== false; // Default to true
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 || 30000,
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
- return this.apiClient.post('/api/public/v1/checkout/pay-v2', {
99
- checkoutSessionId,
100
- paymentInstrumentId,
101
- ...(threedsSessionId && { threedsSessionId }),
102
- ...(options.initiatedBy && { initiatedBy: options.initiatedBy }),
103
- ...(options.source && { source: options.source }),
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
- * Loads /config/resources.static.json
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
- // Load static resources file
103
- console.log('🛠️ [V2] Attempting to load /config/resources.static.json...');
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] resources.static.json not found or failed to load');
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 local static resources:', staticResources);
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
  /**