@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/react/hooks/useCheckout.js +19 -2
- package/dist/react/hooks/useCheckoutSession.d.ts +19 -0
- package/dist/react/hooks/useCheckoutSession.js +108 -0
- package/dist/react/hooks/useCheckoutToken.d.ts +17 -0
- package/dist/react/hooks/useCheckoutToken.js +80 -0
- package/dist/react/hooks/useOrderBump.js +92 -13
- package/dist/react/hooks/useOrderBumpV2.d.ts +17 -0
- package/dist/react/hooks/useOrderBumpV2.js +95 -0
- package/dist/react/hooks/useOrderBumpV3.d.ts +23 -0
- package/dist/react/hooks/useOrderBumpV3.js +109 -0
- package/dist/react/hooks/usePostPurchases.js +11 -5
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +4 -0
- package/dist/react/services/apiService.d.ts +1 -0
- package/dist/react/services/apiService.js +3 -0
- package/dist/v2/core/googleAutocomplete.d.ts +65 -0
- package/dist/v2/core/googleAutocomplete.js +94 -0
- package/dist/v2/core/index.d.ts +8 -0
- package/dist/v2/core/index.js +11 -0
- package/dist/v2/core/isoData.d.ts +50 -0
- package/dist/v2/core/isoData.js +103 -0
- package/dist/v2/core/resources/apiClient.d.ts +25 -0
- package/dist/v2/core/resources/apiClient.js +95 -0
- package/dist/v2/core/resources/checkout.d.ts +189 -0
- package/dist/v2/core/resources/checkout.js +119 -0
- package/dist/v2/core/resources/index.d.ts +13 -0
- package/dist/v2/core/resources/index.js +13 -0
- package/dist/v2/core/resources/offers.d.ts +98 -0
- package/dist/v2/core/resources/offers.js +115 -0
- package/dist/v2/core/resources/orders.d.ts +40 -0
- package/dist/v2/core/resources/orders.js +59 -0
- package/dist/v2/core/resources/payments.d.ts +140 -0
- package/dist/v2/core/resources/payments.js +126 -0
- package/dist/v2/core/resources/postPurchases.d.ts +182 -0
- package/dist/v2/core/resources/postPurchases.js +116 -0
- package/dist/v2/core/resources/products.d.ts +29 -0
- package/dist/v2/core/resources/products.js +49 -0
- package/dist/v2/core/resources/promotions.d.ts +45 -0
- package/dist/v2/core/resources/promotions.js +87 -0
- package/dist/v2/core/resources/threeds.d.ts +23 -0
- package/dist/v2/core/resources/threeds.js +15 -0
- package/dist/v2/core/utils/checkout.d.ts +24 -0
- package/dist/v2/core/utils/checkout.js +30 -0
- package/dist/v2/core/utils/currency.d.ts +28 -0
- package/dist/v2/core/utils/currency.js +272 -0
- package/dist/v2/core/utils/index.d.ts +12 -0
- package/dist/v2/core/utils/index.js +12 -0
- package/dist/v2/core/utils/order.d.ts +159 -0
- package/dist/v2/core/utils/order.js +42 -0
- package/dist/v2/core/utils/orderBump.d.ts +40 -0
- package/dist/v2/core/utils/orderBump.js +47 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +43 -0
- package/dist/v2/core/utils/pluginConfig.js +155 -0
- package/dist/v2/core/utils/postPurchases.d.ts +32 -0
- package/dist/v2/core/utils/postPurchases.js +42 -0
- package/dist/v2/core/utils/products.d.ts +58 -0
- package/dist/v2/core/utils/products.js +64 -0
- package/dist/v2/core/utils/promotions.d.ts +24 -0
- package/dist/v2/core/utils/promotions.js +30 -0
- package/dist/v2/index.d.ts +19 -0
- package/dist/v2/index.js +15 -0
- package/dist/v2/react/components/DebugDrawer.d.ts +7 -0
- package/dist/v2/react/components/DebugDrawer.js +383 -0
- package/dist/v2/react/hooks/useApiQuery.d.ts +28 -0
- package/dist/v2/react/hooks/useApiQuery.js +84 -0
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +39 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +194 -0
- package/dist/v2/react/hooks/useCheckoutToken.d.ts +17 -0
- package/dist/v2/react/hooks/useCheckoutToken.js +65 -0
- package/dist/v2/react/hooks/useCurrency.d.ts +9 -0
- package/dist/v2/react/hooks/useCurrency.js +21 -0
- package/dist/v2/react/hooks/useGeoLocation.d.ts +138 -0
- package/dist/v2/react/hooks/useGeoLocation.js +123 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +74 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +196 -0
- package/dist/v2/react/hooks/useISOData.d.ts +61 -0
- package/dist/v2/react/hooks/useISOData.js +175 -0
- package/dist/v2/react/hooks/useOffersQuery.d.ts +65 -0
- package/dist/v2/react/hooks/useOffersQuery.js +342 -0
- package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +20 -0
- package/dist/v2/react/hooks/useOrderBumpQuery.js +92 -0
- package/dist/v2/react/hooks/useOrderQuery.d.ts +29 -0
- package/dist/v2/react/hooks/useOrderQuery.js +98 -0
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +45 -0
- package/dist/v2/react/hooks/usePaymentPolling.js +140 -0
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +19 -0
- package/dist/v2/react/hooks/usePaymentQuery.js +272 -0
- package/dist/v2/react/hooks/usePluginConfig.d.ts +16 -0
- package/dist/v2/react/hooks/usePluginConfig.js +35 -0
- package/dist/v2/react/hooks/usePostPurchasesQuery.d.ts +63 -0
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +343 -0
- package/dist/v2/react/hooks/useProductsQuery.d.ts +31 -0
- package/dist/v2/react/hooks/useProductsQuery.js +102 -0
- package/dist/v2/react/hooks/usePromotionsQuery.d.ts +28 -0
- package/dist/v2/react/hooks/usePromotionsQuery.js +97 -0
- package/dist/v2/react/hooks/useThreeds.d.ts +36 -0
- package/dist/v2/react/hooks/useThreeds.js +150 -0
- package/dist/v2/react/hooks/useThreedsModal.d.ts +13 -0
- package/dist/v2/react/hooks/useThreedsModal.js +343 -0
- package/dist/v2/react/index.d.ts +38 -0
- package/dist/v2/react/index.js +27 -0
- package/dist/v2/react/providers/TagadaProvider.d.ts +63 -0
- package/dist/v2/react/providers/TagadaProvider.js +680 -0
- 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
|
+
}
|