@tagadapay/plugin-sdk 2.8.8 → 2.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/config/environment.d.ts +1 -22
- package/dist/react/config/environment.js +1 -132
- package/dist/react/utils/deviceInfo.d.ts +1 -39
- package/dist/react/utils/deviceInfo.js +1 -163
- package/dist/react/utils/jwtDecoder.d.ts +1 -14
- package/dist/react/utils/jwtDecoder.js +1 -86
- package/dist/react/utils/tokenStorage.d.ts +1 -16
- package/dist/react/utils/tokenStorage.js +1 -53
- package/dist/v2/core/client.d.ts +92 -0
- package/dist/v2/core/client.js +386 -0
- package/dist/v2/core/config/environment.d.ts +22 -0
- package/dist/v2/core/config/environment.js +140 -0
- package/dist/v2/core/pathRemapping.js +61 -3
- package/dist/v2/core/resources/apiClient.d.ts +8 -0
- package/dist/v2/core/resources/apiClient.js +30 -9
- package/dist/v2/core/resources/funnel.d.ts +14 -0
- package/dist/v2/core/resources/payments.d.ts +23 -0
- package/dist/v2/core/types.d.ts +271 -0
- package/dist/v2/core/types.js +4 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
- package/dist/v2/core/utils/deviceInfo.js +162 -0
- package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
- package/dist/v2/core/utils/eventDispatcher.js +24 -0
- package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
- package/dist/v2/core/utils/jwtDecoder.js +85 -0
- package/dist/v2/core/utils/pluginConfig.js +6 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
- package/dist/v2/core/utils/tokenStorage.js +52 -0
- package/dist/v2/react/components/DebugDrawer.js +90 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +1 -1
- package/dist/v2/react/hooks/useFunnel.js +209 -32
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
- package/dist/v2/react/hooks/useISOData.js +4 -2
- package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
- package/dist/v2/react/hooks/useOffersQuery.js +164 -204
- package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
- package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
- package/dist/v2/react/providers/TagadaProvider.js +79 -673
- 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,
|
|
8
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
}, [
|
|
165
|
-
//
|
|
166
|
-
const
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
const [
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
//
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
}, [
|
|
597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
667
|
-
const
|
|
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
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
730
|
-
pluginConfig,
|
|
731
|
-
configLoading,
|
|
169
|
+
updateFunnelDebugData,
|
|
732
170
|
refreshCoordinator,
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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,
|
|
746
|
-
gcTime: 5 * 60 * 1000,
|
|
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
|
-
|
|
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
|
-
}
|