@tagadapay/plugin-sdk 2.8.8 → 2.8.10

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 (43) hide show
  1. package/dist/react/config/environment.d.ts +1 -22
  2. package/dist/react/config/environment.js +1 -132
  3. package/dist/react/utils/deviceInfo.d.ts +1 -39
  4. package/dist/react/utils/deviceInfo.js +1 -163
  5. package/dist/react/utils/jwtDecoder.d.ts +1 -14
  6. package/dist/react/utils/jwtDecoder.js +1 -86
  7. package/dist/react/utils/tokenStorage.d.ts +1 -16
  8. package/dist/react/utils/tokenStorage.js +1 -53
  9. package/dist/v2/core/client.d.ts +96 -0
  10. package/dist/v2/core/client.js +430 -0
  11. package/dist/v2/core/config/environment.d.ts +36 -0
  12. package/dist/v2/core/config/environment.js +155 -0
  13. package/dist/v2/core/pathRemapping.js +61 -3
  14. package/dist/v2/core/resources/apiClient.d.ts +13 -0
  15. package/dist/v2/core/resources/apiClient.js +77 -9
  16. package/dist/v2/core/resources/funnel.d.ts +21 -0
  17. package/dist/v2/core/resources/payments.d.ts +23 -0
  18. package/dist/v2/core/types.d.ts +271 -0
  19. package/dist/v2/core/types.js +4 -0
  20. package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
  21. package/dist/v2/core/utils/deviceInfo.js +162 -0
  22. package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
  23. package/dist/v2/core/utils/eventDispatcher.js +24 -0
  24. package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
  25. package/dist/v2/core/utils/jwtDecoder.js +85 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
  27. package/dist/v2/core/utils/pluginConfig.js +64 -8
  28. package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
  29. package/dist/v2/core/utils/tokenStorage.js +52 -0
  30. package/dist/v2/react/components/ApplePayButton.js +1 -1
  31. package/dist/v2/react/components/DebugDrawer.js +90 -1
  32. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
  34. package/dist/v2/react/hooks/useFunnel.d.ts +2 -1
  35. package/dist/v2/react/hooks/useFunnel.js +245 -69
  36. package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
  37. package/dist/v2/react/hooks/useISOData.js +4 -2
  38. package/dist/v2/react/hooks/useOffersQuery.d.ts +42 -29
  39. package/dist/v2/react/hooks/useOffersQuery.js +266 -204
  40. package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
  41. package/dist/v2/react/providers/TagadaProvider.d.ts +13 -21
  42. package/dist/v2/react/providers/TagadaProvider.js +79 -673
  43. package/package.json +1 -1
@@ -4,16 +4,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * TagadaProvider - Main provider component for the Tagada Pay React SDK
5
5
  */
6
6
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
8
- import { detectEnvironment, getEnvironmentConfig } from '../../../react/config/environment';
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, useRef, } from 'react';
8
+ import { TagadaClient } from '../../core/client';
9
9
  import { ApiService } from '../../../react/services/apiService';
10
- import { collectDeviceInfo, getBrowserLocale, getUrlParams } from '../../../react/utils/deviceInfo';
11
- import { decodeJWTClient, isTokenExpired } from '../../../react/utils/jwtDecoder';
12
- import { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, } from '../../../react/utils/money';
13
- import { clearClientToken, getClientToken, setClientToken } from '../../../react/utils/tokenStorage';
14
- import { ApiClient } from '../../core/resources/apiClient';
15
- import { resolveEnvValue } from '../../core/utils/env';
16
- import { loadPluginConfig } from '../../core/utils/pluginConfig';
10
+ import { formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, convertCurrency, } from '../../../react/utils/money';
17
11
  import { default as DebugDrawer } from '../components/DebugDrawer';
18
12
  import { setGlobalApiClient } from '../hooks/useApiQuery';
19
13
  // Professional, subtle loading component for initialization
@@ -48,248 +42,47 @@ const InitializationLoader = () => (_jsxs("div", { style: {
48
42
  100% { transform: rotate(360deg); }
49
43
  }
50
44
  ` })] }));
51
- const isEnvironment = (value) => value === 'production' || value === 'development' || value === 'local';
52
- const resolveEnvironmentFromVariables = () => {
53
- const envValue = resolveEnvValue('TAGADA_ENV') ?? resolveEnvValue('TAGADA_ENVIRONMENT');
54
- if (!envValue) {
55
- return undefined;
56
- }
57
- const normalized = envValue.trim().toLowerCase();
58
- return isEnvironment(normalized) ? normalized : undefined;
59
- };
60
45
  const TagadaContext = createContext(null);
61
- // Global instance tracking for TagadaProvider
62
- let globalTagadaInstance = null;
63
- let globalTagadaInitialized = false;
64
- export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
65
- localConfig, blockUntilSessionReady = false, // Default to new non-blocking behavior
66
- rawPluginConfig, }) {
67
- // Instance tracking
68
- const [instanceId] = useState(() => {
69
- if (!globalTagadaInstance) {
70
- globalTagadaInstance = Math.random().toString(36).substr(2, 9);
71
- }
72
- return globalTagadaInstance;
73
- });
74
- const isActiveInstance = useMemo(() => {
75
- if (!globalTagadaInitialized) {
76
- globalTagadaInitialized = true;
77
- console.log(`✅ [TagadaProvider] Instance ${instanceId} is now the active instance`);
78
- return true;
79
- }
80
- else {
81
- console.log(`🚫 [TagadaProvider] Instance ${instanceId} is duplicate - blocking execution`);
82
- return false;
83
- }
84
- }, [instanceId]);
85
- useEffect(() => {
86
- return () => {
87
- if (globalTagadaInstance === instanceId) {
88
- globalTagadaInitialized = false;
89
- globalTagadaInstance = null;
90
- console.log(`🧹 [TagadaProvider] Instance ${instanceId} cleanup - allowing new instances`);
91
- }
92
- };
93
- }, [instanceId]);
94
- // LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
95
- const isLocalDev = typeof window !== 'undefined' &&
96
- (window.location.hostname === 'localhost' ||
97
- window.location.hostname.includes('.localhost') ||
98
- window.location.hostname.includes('127.0.0.1'));
99
- const configVariant = isLocalDev ? localConfig || 'default' : 'default';
100
- // Debug logging (only log once during initial render)
101
- const hasLoggedRef = useRef(false);
102
- if (!hasLoggedRef.current) {
103
- console.log(`🔍 [TagadaProvider] Instance ${instanceId} Config Debug:`, {
104
- hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
105
- isLocalDev,
46
+ export function TagadaProvider({ children, environment, customApiConfig, // Ignored for now in TagadaClient, or need to add support
47
+ debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, }) {
48
+ // Initialize client
49
+ const client = useMemo(() => {
50
+ const config = {
51
+ environment,
52
+ debugMode,
106
53
  localConfig,
107
- configVariant,
108
- });
109
- hasLoggedRef.current = true;
110
- }
111
- // Load plugin configuration directly (not using hook to avoid circular dependency)
112
- // Initialize with raw config if available to avoid empty config during loading
113
- const [pluginConfig, setPluginConfig] = useState(() => {
114
- if (rawPluginConfig) {
115
- return {
116
- storeId: rawPluginConfig.storeId,
117
- accountId: rawPluginConfig.accountId,
118
- basePath: rawPluginConfig.basePath ?? '/',
119
- config: rawPluginConfig.config ?? {},
120
- };
121
- }
122
- return { basePath: '/', config: {} };
123
- });
124
- const [configLoading, setConfigLoading] = useState(!rawPluginConfig);
125
- // Load plugin config on mount with the specified variant
126
- useEffect(() => {
127
- // Prevent multiple config loads
128
- if (configLoading === false && pluginConfig.storeId) {
129
- console.log('🔒 [TagadaProvider] Config already loaded, skipping reload');
130
- return;
131
- }
132
- const loadConfig = async () => {
133
- try {
134
- // Use the v2 core loadPluginConfig function
135
- const config = await loadPluginConfig(configVariant, rawPluginConfig);
136
- // Ensure we have required store ID before proceeding
137
- if (!config.storeId) {
138
- console.warn('⚠️ No store ID found in plugin config. This may cause hooks to fail.');
139
- }
140
- setPluginConfig(config);
141
- console.log('✅ Phase 1 & 2 Complete - Plugin config loaded:', {
142
- storeId: config.storeId,
143
- accountId: config.accountId,
144
- basePath: config.basePath,
145
- hasConfig: !!config.config,
146
- source: rawPluginConfig ? 'raw' : 'file',
147
- });
148
- if (blockUntilSessionReady) {
149
- console.log('⏳ Blocking mode: Children will render after Phase 3 (session init) completes');
150
- }
151
- else {
152
- console.log('🚀 Non-blocking mode: Children can now render - Phase 3 (session init) will continue in background');
153
- }
154
- }
155
- catch (error) {
156
- console.error('❌ Failed to load plugin config in TagadaProvider:', error);
157
- setPluginConfig({ basePath: '/', config: {} });
158
- }
159
- finally {
160
- setConfigLoading(false);
161
- }
54
+ rawPluginConfig,
55
+ blockUntilSessionReady,
162
56
  };
163
- void loadConfig();
164
- }, [configVariant, rawPluginConfig]);
165
- // Extract store/account IDs from plugin config (only source now)
166
- const storeId = pluginConfig.storeId;
167
- const [isLoading, setIsLoading] = useState(true);
168
- const [isInitialized, setIsInitialized] = useState(false);
169
- const [token, setToken] = useState(null);
170
- const [hasAttemptedAnonymousToken, setHasAttemptedAnonymousToken] = useState(false);
171
- const [isDebugDrawerOpen, setIsDebugDrawerOpen] = useState(false);
172
- const isInitializing = useRef(false);
173
- // Initialize environment configuration
174
- const [environmentConfig, _setEnvironmentConfig] = useState(() => {
175
- const envFromVariables = resolveEnvironmentFromVariables();
176
- const detectedEnv = environment || envFromVariables || detectEnvironment();
177
- const config = getEnvironmentConfig(detectedEnv);
178
- console.log('envFromVariables', envFromVariables);
179
- console.log('detectedEnv', detectedEnv);
180
- // Log environment detection for debugging
181
- if (environment) {
182
- console.log(`[TagadaSDK] Using explicit environment: ${environment}`);
183
- }
184
- else if (envFromVariables) {
185
- console.log(`[TagadaSDK] Using environment from env variables: ${envFromVariables}`);
186
- }
187
- else {
188
- console.log(`[TagadaSDK] Auto-detected environment: ${detectedEnv} (${typeof window !== 'undefined' ? window.location.hostname : 'SSR'})`);
189
- }
190
- // Apply custom API config if provided
191
- if (customApiConfig) {
192
- return {
193
- ...config,
194
- ...customApiConfig,
195
- apiConfig: {
196
- ...config.apiConfig,
197
- ...customApiConfig.apiConfig,
198
- },
199
- };
200
- }
201
- return config;
202
- });
203
- // Auto-set debugMode based on environment (unless explicitly provided)
204
- const finalDebugMode = debugMode ?? environmentConfig.environment !== 'production';
205
- // Initialize API service
206
- const [apiService] = useState(() => {
207
- const service = new ApiService({
208
- environmentConfig,
209
- token,
210
- onTokenUpdate: (newToken) => {
211
- setToken(newToken);
212
- setClientToken(newToken);
213
- },
214
- onTokenClear: () => {
215
- setToken(null);
216
- clearClientToken();
217
- },
218
- });
219
- // Store ID is managed by plugin configuration system
220
- if (storeId) {
221
- console.log(`[SDK] Using store ID from config: ${storeId}`);
222
- }
223
- return service;
224
- });
225
- // Initialize TanStack Query API client synchronously during provider initialization
226
- // This ensures the global client is available immediately for hooks that use it in useMemo
227
- const [apiClient] = useState(() => {
228
- const client = new ApiClient({
229
- baseURL: environmentConfig.apiConfig.baseUrl,
230
- headers: {
231
- 'Content-Type': 'application/json',
232
- },
233
- });
234
- // Set the global client for TanStack Query hooks
235
- setGlobalApiClient(client);
236
- // Check for existing token and set it immediately
237
- const existingToken = getClientToken();
238
- if (existingToken && !isTokenExpired(existingToken)) {
239
- client.updateToken(existingToken);
240
- console.log('[SDK] ApiClient initialized with existing token:', existingToken.substring(0, 8) + '...');
241
- }
242
- else {
243
- console.log('[SDK] ApiClient initialized with baseURL:', environmentConfig.apiConfig.baseUrl);
244
- }
245
- return client;
246
- });
247
- // Update API client when environment config changes
57
+ return new TagadaClient(config);
58
+ }, []); // Singleton behavior
59
+ // State Sync
60
+ const [state, setState] = useState(client.getState());
248
61
  useEffect(() => {
249
- apiClient.updateConfig({
250
- baseURL: environmentConfig.apiConfig.baseUrl,
251
- headers: {
252
- 'Content-Type': 'application/json',
253
- },
254
- });
255
- console.log('[SDK] ApiClient config updated with baseURL:', environmentConfig.apiConfig.baseUrl);
256
- }, [environmentConfig, apiClient]);
257
- // Sync token updates between ApiService and ApiClient
62
+ return client.subscribe(setState);
63
+ }, [client]);
64
+ // Cleanup client on unmount
258
65
  useEffect(() => {
259
- // Always use the token from ApiService as the source of truth
260
- const currentToken = apiService.getCurrentToken();
261
- if (currentToken && typeof currentToken === 'string') {
262
- apiClient.updateToken(currentToken);
263
- console.log('[SDK] Token synced to ApiClient:', currentToken.substring(0, 8) + '...');
264
- }
265
- else {
266
- apiClient.updateToken(null);
267
- console.log('[SDK] Token cleared from ApiClient');
268
- }
269
- }, [token, apiService, apiClient]);
270
- // Update API service when config or token changes
66
+ return () => {
67
+ client.destroy();
68
+ };
69
+ }, [client]);
70
+ // Sync global API client for hooks
271
71
  useEffect(() => {
272
- apiService.updateConfig(environmentConfig);
273
- apiService.updateToken(token);
274
- }, [apiService, environmentConfig, token]);
275
- // Initialize state
276
- const [customer, setCustomer] = useState(null);
277
- const [session, setSession] = useState(null);
278
- const [store, setStore] = useState(null);
279
- const [isSessionInitialized, setIsSessionInitialized] = useState(false);
280
- // Initialize locale and currency with defaults
281
- const [locale, setLocale] = useState({
282
- locale: 'en-US',
283
- language: 'en',
284
- region: 'US',
285
- messages: {},
286
- });
287
- const [currency, setCurrency] = useState({
288
- code: 'USD',
289
- symbol: '$',
290
- name: 'US Dollar',
291
- });
292
- // Initialize debug checkout state
72
+ setGlobalApiClient(client.apiClient);
73
+ }, [client]);
74
+ // Initialize ApiService for backward compatibility
75
+ // We use state.environment to keep it updated
76
+ const [apiService] = useState(() => new ApiService({
77
+ environmentConfig: state.environment,
78
+ token: client.apiClient.getCurrentToken(),
79
+ }));
80
+ // Sync token and config to ApiService
81
+ useEffect(() => {
82
+ apiService.updateToken(state.token);
83
+ apiService.updateConfig(state.environment);
84
+ }, [state.token, state.environment, apiService]);
85
+ // Debug State (React specific)
293
86
  const [debugCheckout, setDebugCheckout] = useState({
294
87
  isActive: false,
295
88
  data: null,
@@ -304,367 +97,60 @@ rawPluginConfig, }) {
304
97
  isLoading: false,
305
98
  lastUpdated: null,
306
99
  });
307
- // Initialize auth state
308
- const [auth, setAuth] = useState({
309
- isAuthenticated: false,
310
- isLoading: false,
311
- customer: null,
312
- session: null,
313
- });
314
- // Initialize session
315
- const initializeSession = useCallback(async (sessionData) => {
316
- if (!isActiveInstance) {
317
- console.log(`🚫 [TagadaProvider] Instance ${instanceId} is not active, skipping session initialization`);
318
- return;
319
- }
320
- if (!sessionData.storeId || !sessionData.accountId) {
321
- console.error(`[TagadaProvider] Instance ${instanceId} missing required session data`);
322
- return;
323
- }
324
- // Prevent multiple session initializations
325
- if (isSessionInitialized) {
326
- console.log(`🔒 [TagadaProvider] Instance ${instanceId} session already initialized, skipping`);
327
- return;
328
- }
329
- if (finalDebugMode) {
330
- console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Initializing session with store config...`, sessionData);
331
- }
332
- setIsLoading(true);
333
- try {
334
- const deviceInfo = collectDeviceInfo();
335
- const urlParams = getUrlParams();
336
- const browserLocale = getBrowserLocale();
337
- const sessionInitData = {
338
- storeId: sessionData.storeId,
339
- accountId: sessionData.accountId,
340
- customerId: sessionData.customerId,
341
- role: sessionData.role,
342
- browserLocale,
343
- queryLocale: urlParams.locale,
344
- queryCurrency: urlParams.currency,
345
- utmSource: urlParams.utmSource,
346
- utmMedium: urlParams.utmMedium,
347
- utmCampaign: urlParams.utmCampaign,
348
- browser: deviceInfo.userAgent.browser.name,
349
- browserVersion: deviceInfo.userAgent.browser.version,
350
- os: deviceInfo.userAgent.os.name,
351
- osVersion: deviceInfo.userAgent.os.version,
352
- deviceType: deviceInfo.userAgent.device?.type,
353
- deviceModel: deviceInfo.userAgent.device?.model,
354
- screenWidth: deviceInfo.screenResolution.width,
355
- screenHeight: deviceInfo.screenResolution.height,
356
- timeZone: deviceInfo.timeZone,
357
- };
358
- if (finalDebugMode) {
359
- console.debug('[SDK][DEBUG] Session init data:', sessionInitData);
360
- }
361
- const response = await apiService.initializeSession(sessionInitData);
362
- if (finalDebugMode) {
363
- console.debug('[SDK][DEBUG] Session init response:', response);
364
- }
365
- // Update store data
366
- if (response.store) {
367
- const storeData = response.store;
368
- const storeConfig = {
369
- ...response.store,
370
- presentmentCurrencies: storeData.presentmentCurrencies || [response.store.currency || 'USD'],
371
- chargeCurrencies: storeData.chargeCurrencies || [response.store.currency || 'USD'],
372
- };
373
- setStore(storeConfig);
374
- if (finalDebugMode) {
375
- console.debug('[SDK][DEBUG] Store config loaded:', storeConfig);
376
- }
377
- }
378
- // Update locale
379
- const localeConfig = {
380
- locale: response.locale,
381
- language: response.locale.split('-')[0],
382
- region: response.locale.split('-')[1] ?? 'US',
383
- messages: response.messages ?? {},
384
- };
385
- setLocale(localeConfig);
386
- if (finalDebugMode) {
387
- console.debug('[SDK][DEBUG] Locale config:', localeConfig);
388
- }
389
- // Update currency based on store
390
- if (response.store) {
391
- const currencyConfig = {
392
- code: response.store.currency,
393
- symbol: getCurrencySymbol(response.store.currency),
394
- name: getCurrencyName(response.store.currency),
395
- };
396
- setCurrency(currencyConfig);
397
- if (finalDebugMode) {
398
- console.debug('[SDK][DEBUG] Currency config:', currencyConfig);
399
- }
400
- }
401
- // Update customer data if available
402
- if (response.customer) {
403
- setCustomer(response.customer);
404
- if (finalDebugMode) {
405
- console.debug('[SDK][DEBUG] Customer data:', response.customer);
406
- }
407
- }
408
- // Update auth state
409
- const authState = {
410
- isAuthenticated: response.customer?.isAuthenticated ?? false,
411
- isLoading: false,
412
- customer: response.customer ?? null,
413
- session: sessionData,
414
- };
415
- setAuth(authState);
416
- if (finalDebugMode) {
417
- console.debug('[SDK][DEBUG] Auth state:', authState);
418
- }
419
- console.debug('[SDK] Session initialized successfully');
420
- setIsInitialized(true);
421
- setIsSessionInitialized(true); // Mark CMS session as ready
422
- setIsLoading(false);
423
- }
424
- catch (error) {
425
- console.error('[SDK] Error initializing session:', error);
426
- if (finalDebugMode) {
427
- console.debug('[SDK][DEBUG] Full error details:', error);
428
- }
429
- setIsInitialized(true);
430
- setIsLoading(false);
431
- }
432
- }, [apiService, finalDebugMode, isActiveInstance, instanceId]);
433
- // Create anonymous token if needed
434
- const createAnonymousToken = useCallback(async (targetStoreId) => {
435
- if (!isActiveInstance) {
436
- console.log(`🚫 [TagadaProvider] Instance ${instanceId} is not active, skipping anonymous token creation`);
437
- return;
438
- }
439
- if (hasAttemptedAnonymousToken || !targetStoreId) {
440
- console.log(`🔒 [TagadaProvider] Instance ${instanceId} anonymous token already attempted or no storeId:`, {
441
- hasAttemptedAnonymousToken,
442
- targetStoreId,
443
- });
444
- return;
445
- }
446
- console.log(`[TagadaProvider] Instance ${instanceId} 🚀 Starting Phase 3 - Session initialization...`);
447
- if (finalDebugMode) {
448
- console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Creating anonymous token for store:`, targetStoreId);
449
- }
450
- setHasAttemptedAnonymousToken(true);
451
- try {
452
- const response = await apiService.createAnonymousToken(targetStoreId);
453
- if (finalDebugMode) {
454
- console.debug('[SDK][DEBUG] Anonymous token response:', response);
455
- }
456
- // Set the token in state and storage
457
- setToken(response.token);
458
- setClientToken(response.token);
459
- // Update the API service with the new token
460
- apiService.updateToken(response.token);
461
- // IMPORTANT: Immediately sync token to API client before marking session as ready
462
- // This ensures any queries that become enabled after isSessionInitialized=true have the token
463
- apiClient.updateToken(response.token);
464
- console.log('[SDK] Token immediately synced to ApiClient:', response.token.substring(0, 8) + '...');
465
- // Decode the token to get session data
466
- const decodedSession = decodeJWTClient(response.token);
467
- if (decodedSession) {
468
- if (finalDebugMode) {
469
- console.debug('[SDK][DEBUG] Decoded session from token:', decodedSession);
470
- }
471
- setSession(decodedSession);
472
- // Initialize session with API call
473
- await initializeSession(decodedSession);
474
- }
475
- else {
476
- console.error('[SDK] Failed to decode anonymous token');
477
- setIsInitialized(true);
478
- setIsLoading(false);
479
- }
480
- console.log('[SDK] ✅ Phase 3 Complete - Session initialization completed successfully');
481
- setIsSessionInitialized(true); // Mark CMS session as ready
482
- }
483
- catch (error) {
484
- console.error('[SDK] ❌ Initialization failed:', error);
485
- if (finalDebugMode) {
486
- console.debug('[SDK][DEBUG] Anonymous token creation failed:', error);
487
- }
488
- setIsInitialized(true);
489
- setIsLoading(false);
490
- }
491
- }, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode, isActiveInstance, instanceId]);
492
- // Initialize token from storage or create anonymous token (extracted to stable callback)
493
- const initializeToken = useCallback(async () => {
494
- if (!isActiveInstance) {
495
- console.log(`🚫 [TagadaProvider] Instance ${instanceId} is not active, skipping token initialization`);
496
- return;
497
- }
498
- try {
499
- console.debug('[SDK] Initializing token...');
500
- setIsLoading(true);
501
- // Check for existing token
502
- const existingToken = getClientToken();
503
- let tokenToUse = null;
504
- // Check URL params for token
505
- const urlParams = new URLSearchParams(window.location.search);
506
- const queryToken = urlParams.get('token');
507
- if (queryToken) {
508
- console.debug('[SDK] Found token in URL params');
509
- tokenToUse = queryToken;
510
- setClientToken(queryToken);
511
- }
512
- else if (existingToken && !isTokenExpired(existingToken)) {
513
- console.debug('[SDK] Using existing token from storage');
514
- tokenToUse = existingToken;
515
- }
516
- else {
517
- console.debug('[SDK] No valid token found');
518
- // Determine storeId for anonymous token
519
- const targetStoreId = storeId || 'default-store';
520
- await createAnonymousToken(targetStoreId);
521
- return;
522
- }
523
- if (tokenToUse) {
524
- setToken(tokenToUse);
525
- // Update the API service with the token
526
- apiService.updateToken(tokenToUse);
527
- // IMPORTANT: Immediately sync token to API client
528
- apiClient.updateToken(tokenToUse);
529
- console.log('[SDK] Token immediately synced to ApiClient:', tokenToUse.substring(0, 8) + '...');
530
- // Decode token to get session data
531
- const decodedSession = decodeJWTClient(tokenToUse);
532
- if (decodedSession) {
533
- setSession(decodedSession);
534
- // Initialize session with API call
535
- await initializeSession(decodedSession);
536
- }
537
- else {
538
- console.error('[SDK] Failed to decode token');
539
- setIsInitialized(true);
540
- setIsSessionInitialized(false); // Session failed to initialize
541
- setIsLoading(false);
542
- }
543
- }
544
- }
545
- catch (error) {
546
- console.error('[SDK] Error initializing token:', error);
547
- setIsInitialized(true);
548
- setIsLoading(false);
549
- }
550
- }, [apiService, apiClient, storeId, createAnonymousToken, initializeSession, isActiveInstance, instanceId]);
551
- // Initialize token from storage or create anonymous token
552
- // This runs in the background after phases 1 & 2 complete, but doesn't block rendering
553
- useEffect(() => {
554
- if (!isActiveInstance) {
555
- console.log(`🚫 [TagadaProvider] Instance ${instanceId} is not active, skipping token initialization`);
556
- return;
557
- }
558
- if (isInitializing.current) {
559
- console.log(`🔒 [TagadaProvider] Instance ${instanceId} already initializing, skipping`);
560
- return;
561
- }
562
- // Wait for plugin config to load AND ensure we have a store ID before initializing
563
- if (configLoading || !storeId) {
564
- console.log(`⏳ [TagadaProvider] Instance ${instanceId} waiting for config or storeId:`, {
565
- configLoading,
566
- storeId,
567
- });
568
- return;
569
- }
570
- isInitializing.current = true;
571
- void initializeToken();
572
- }, [storeId, initializeToken, configLoading, isActiveInstance, instanceId]);
573
- // Listen for storage changes (e.g., token updated in another tab)
574
- useEffect(() => {
575
- if (!isActiveInstance) {
576
- return;
577
- }
578
- function onStorage() {
579
- // Re-run initialization when token may have changed in another tab
580
- isInitializing.current = false;
581
- void initializeToken();
582
- }
583
- window.addEventListener('storage', onStorage);
584
- return () => {
585
- window.removeEventListener('storage', onStorage);
586
- };
587
- }, [initializeToken, isActiveInstance]);
588
- // Update auth state when customer/session changes
589
- useEffect(() => {
590
- setAuth({
591
- isAuthenticated: customer?.isAuthenticated ?? false,
592
- isLoading: false,
593
- customer,
594
- session,
100
+ // Debug Data Updaters
101
+ const updateCheckoutDebugData = useCallback((data, error, isLoading) => {
102
+ setDebugCheckout({
103
+ isActive: true,
104
+ data,
105
+ error: error ?? null,
106
+ isLoading: isLoading ?? false,
107
+ lastUpdated: new Date(),
595
108
  });
596
- }, [customer, session]);
597
- // Refresh coordinator for bidirectional hook communication
109
+ }, []);
110
+ const updateFunnelDebugData = useCallback((data, error, isLoading) => {
111
+ setDebugFunnel({
112
+ isActive: true,
113
+ data,
114
+ error: error ?? null,
115
+ isLoading: isLoading ?? false,
116
+ lastUpdated: new Date(),
117
+ });
118
+ }, []);
119
+ // Refresh Coordinator
598
120
  const checkoutRefreshRefs = useRef(new Set());
599
121
  const orderBumpRefreshRefs = useRef(new Set());
600
122
  const refreshCoordinator = useMemo(() => ({
601
123
  registerCheckoutRefresh: (refreshFn, name) => {
602
- const hookName = name || 'unknown';
603
- console.log(`🔄 [RefreshCoordinator] Registering checkout refresh function from ${hookName}:`, refreshFn.toString().substring(0, 100) + '...');
604
124
  checkoutRefreshRefs.current.add(refreshFn);
605
- console.log(`✅ [RefreshCoordinator] Checkout refresh function registered from ${hookName} (total: ${checkoutRefreshRefs.current.size})`);
606
125
  },
607
126
  registerOrderBumpRefresh: (refreshFn) => {
608
127
  orderBumpRefreshRefs.current.add(refreshFn);
609
- console.log('🔄 [RefreshCoordinator] Order bump refresh function registered');
610
128
  },
611
129
  notifyCheckoutChanged: async () => {
612
130
  if (orderBumpRefreshRefs.current.size > 0) {
613
- console.log('🔄 [RefreshCoordinator] Checkout changed, refreshing order bump data...');
614
- const refreshPromises = Array.from(orderBumpRefreshRefs.current).map(async (refreshFn) => {
615
- try {
616
- await refreshFn();
617
- }
618
- catch (error) {
619
- console.warn('⚠️ [RefreshCoordinator] Order bump refresh failed:', error);
620
- }
621
- });
622
- await Promise.all(refreshPromises);
623
- console.log('✅ [RefreshCoordinator] Order bump refresh completed');
131
+ await Promise.all(Array.from(orderBumpRefreshRefs.current).map((fn) => fn().catch(console.warn)));
624
132
  }
625
133
  },
626
134
  notifyOrderBumpChanged: async () => {
627
- console.log(`🔄 [RefreshCoordinator] Order bump changed, refreshing checkout data... (${checkoutRefreshRefs.current.size} functions registered)`);
628
135
  if (checkoutRefreshRefs.current.size > 0) {
629
- const refreshPromises = Array.from(checkoutRefreshRefs.current).map(async (refreshFn, index) => {
630
- try {
631
- console.log(`🔄 [RefreshCoordinator] Calling checkout refresh function ${index + 1}/${checkoutRefreshRefs.current.size}`);
632
- await refreshFn();
633
- console.log(`✅ [RefreshCoordinator] Checkout refresh function ${index + 1} completed`);
634
- }
635
- catch (error) {
636
- console.warn(`⚠️ [RefreshCoordinator] Checkout refresh function ${index + 1} failed:`, error);
637
- }
638
- });
639
- await Promise.all(refreshPromises);
640
- console.log('✅ [RefreshCoordinator] All checkout refreshes completed');
641
- }
642
- else {
643
- console.warn('⚠️ [RefreshCoordinator] No checkout refresh functions registered!');
136
+ await Promise.all(Array.from(checkoutRefreshRefs.current).map((fn) => fn().catch(console.warn)));
644
137
  }
645
138
  },
646
139
  unregisterCheckoutRefresh: (refreshFn) => {
647
- if (refreshFn) {
140
+ if (refreshFn)
648
141
  checkoutRefreshRefs.current.delete(refreshFn);
649
- console.log(`🧹 [RefreshCoordinator] Checkout refresh function unregistered (remaining: ${checkoutRefreshRefs.current.size})`);
650
- }
651
- else {
142
+ else
652
143
  checkoutRefreshRefs.current.clear();
653
- console.log('🧹 [RefreshCoordinator] All checkout refresh functions unregistered');
654
- }
655
144
  },
656
145
  unregisterOrderBumpRefresh: (refreshFn) => {
657
- if (refreshFn) {
146
+ if (refreshFn)
658
147
  orderBumpRefreshRefs.current.delete(refreshFn);
659
- }
660
- else {
148
+ else
661
149
  orderBumpRefreshRefs.current.clear();
662
- }
663
- console.log('🧹 [RefreshCoordinator] Order bump refresh function unregistered');
664
150
  },
665
151
  }), []);
666
- // Memoize stable values that don't change frequently
667
- const memoizedMoneyUtils = useMemo(() => ({
152
+ // Money Utils
153
+ const moneyUtils = useMemo(() => ({
668
154
  formatMoney,
669
155
  getCurrencyInfo,
670
156
  moneyStringOrNumberToMinorUnits,
@@ -673,77 +159,23 @@ rawPluginConfig, }) {
673
159
  convertCurrency,
674
160
  formatSimpleMoney,
675
161
  }), []);
676
- const memoizedUpdateDebugData = useCallback((data, error, isLoading) => {
677
- setDebugCheckout({
678
- isActive: true,
679
- data,
680
- error: error ?? null,
681
- isLoading: isLoading ?? false,
682
- lastUpdated: new Date(),
683
- });
684
- }, []);
685
- // Memoize the context value to prevent unnecessary re-renders
686
- const contextValue = useMemo(() => ({
687
- auth,
688
- session,
689
- customer,
690
- locale,
691
- currency,
692
- store,
693
- environment: environmentConfig,
694
- apiService,
695
- isLoading,
696
- isInitialized,
697
- isSessionInitialized,
698
- debugMode: finalDebugMode,
699
- pluginConfig,
700
- pluginConfigLoading: configLoading,
701
- debugCheckout,
702
- updateCheckoutDebugData: memoizedUpdateDebugData,
703
- debugFunnel,
704
- updateFunnelDebugData: (data, error, isLoading) => {
705
- setDebugFunnel({
706
- isActive: true,
707
- data,
708
- error: error ?? null,
709
- isLoading: isLoading ?? false,
710
- lastUpdated: new Date(),
711
- });
712
- },
713
- refreshCoordinator,
714
- money: memoizedMoneyUtils,
715
- }), [
716
- auth,
717
- session,
718
- customer,
719
- locale,
720
- currency,
721
- store,
722
- environmentConfig,
162
+ const [isDebugDrawerOpen, setIsDebugDrawerOpen] = useState(false);
163
+ const contextValue = {
164
+ ...state,
723
165
  apiService,
724
- isLoading,
725
- isInitialized,
726
- isSessionInitialized,
727
166
  debugCheckout,
167
+ updateCheckoutDebugData,
728
168
  debugFunnel,
729
- finalDebugMode,
730
- pluginConfig,
731
- configLoading,
169
+ updateFunnelDebugData,
732
170
  refreshCoordinator,
733
- memoizedUpdateDebugData,
734
- memoizedMoneyUtils,
735
- // debugCheckout removed from deps to prevent unnecessary re-renders
736
- ]);
737
- // Determine if we should show loading
738
- // Always block until config is loaded (even if empty)
739
- const shouldShowLoading = configLoading;
740
- const canRenderChildren = !configLoading;
741
- // Initialize TanStack Query client
171
+ money: moneyUtils,
172
+ };
173
+ // Query Client
742
174
  const [queryClient] = useState(() => new QueryClient({
743
175
  defaultOptions: {
744
176
  queries: {
745
- staleTime: 30000, // 30 seconds
746
- gcTime: 5 * 60 * 1000, // 5 minutes
177
+ staleTime: 30000,
178
+ gcTime: 5 * 60 * 1000,
747
179
  refetchOnWindowFocus: false,
748
180
  retry: 1,
749
181
  },
@@ -752,7 +184,10 @@ rawPluginConfig, }) {
752
184
  },
753
185
  },
754
186
  }));
755
- return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(TagadaContext.Provider, { value: contextValue, children: [shouldShowLoading && _jsx(InitializationLoader, {}), finalDebugMode && canRenderChildren && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
187
+ // Loading State Logic
188
+ const shouldShowLoading = state.isLoading || state.pluginConfigLoading || (blockUntilSessionReady && !state.isSessionInitialized);
189
+ const canRenderChildren = !state.pluginConfigLoading && (!blockUntilSessionReady || state.isSessionInitialized);
190
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(TagadaContext.Provider, { value: contextValue, children: [shouldShowLoading && _jsx(InitializationLoader, {}), state.debugMode && canRenderChildren && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
756
191
  position: 'fixed',
757
192
  bottom: '16px',
758
193
  right: '16px',
@@ -767,12 +202,6 @@ rawPluginConfig, }) {
767
202
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
768
203
  zIndex: 999997,
769
204
  transition: 'all 0.2s ease',
770
- }, onMouseEnter: (e) => {
771
- e.currentTarget.style.backgroundColor = '#ea580c';
772
- e.currentTarget.style.transform = 'scale(1.1)';
773
- }, onMouseLeave: (e) => {
774
- e.currentTarget.style.backgroundColor = '#f97316';
775
- e.currentTarget.style.transform = 'scale(1)';
776
205
  }, title: "Open TagadaPay SDK Debug Panel", children: "\uD83D\uDC1B" }), _jsx(DebugDrawer, { isOpen: isDebugDrawerOpen, onClose: () => setIsDebugDrawerOpen(false) })] })), canRenderChildren && children] }) }));
777
206
  }
778
207
  export function useTagadaContext() {
@@ -782,26 +211,3 @@ export function useTagadaContext() {
782
211
  }
783
212
  return context;
784
213
  }
785
- // Helper functions
786
- function getCurrencySymbol(code) {
787
- const symbols = {
788
- USD: '$',
789
- EUR: '€',
790
- GBP: '£',
791
- JPY: '¥',
792
- CAD: 'C$',
793
- AUD: 'A$',
794
- };
795
- return symbols[code] || code;
796
- }
797
- function getCurrencyName(code) {
798
- const names = {
799
- USD: 'US Dollar',
800
- EUR: 'Euro',
801
- GBP: 'British Pound',
802
- JPY: 'Japanese Yen',
803
- CAD: 'Canadian Dollar',
804
- AUD: 'Australian Dollar',
805
- };
806
- return names[code] || code;
807
- }