@tagadapay/plugin-sdk 2.4.39 → 2.5.2

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