@tagadapay/plugin-sdk 2.5.0 โ 2.6.0
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/v2/react/hooks/useCheckoutQuery.js +0 -14
- package/dist/v2/react/hooks/useCheckoutToken.js +1 -16
- package/dist/v2/react/hooks/useGeoLocation.js +0 -3
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +2 -13
- package/dist/v2/react/hooks/useISOData.js +2 -3
- package/dist/v2/react/hooks/useOffersQuery.js +32 -43
- package/dist/v2/react/hooks/useOrderBumpQuery.js +12 -8
- package/dist/v2/react/hooks/usePaymentPolling.js +2 -15
- package/dist/v2/react/hooks/usePaymentQuery.js +17 -28
- package/dist/v2/react/hooks/usePluginConfig.d.ts +2 -0
- package/dist/v2/react/hooks/usePluginConfig.js +3 -2
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +12 -34
- package/dist/v2/react/hooks/useThreeds.js +15 -31
- package/dist/v2/react/providers/TagadaProvider.js +69 -8
- package/package.json +1 -1
|
@@ -34,14 +34,6 @@ export function useCheckoutQuery(options = {}) {
|
|
|
34
34
|
}, [providedToken, internalToken]);
|
|
35
35
|
// Use provided token or internal token
|
|
36
36
|
const checkoutToken = providedToken || internalToken;
|
|
37
|
-
console.log('๐ [useCheckoutQuery] Query setup:', {
|
|
38
|
-
providedToken: providedToken ? providedToken.substring(0, 8) + '...' : 'none',
|
|
39
|
-
internalToken: internalToken ? internalToken.substring(0, 8) + '...' : 'none',
|
|
40
|
-
finalToken: checkoutToken ? checkoutToken.substring(0, 8) + '...' : 'none',
|
|
41
|
-
enabled: enabled && !!checkoutToken && isSessionInitialized,
|
|
42
|
-
currentCurrency: currency.code,
|
|
43
|
-
isSessionInitialized
|
|
44
|
-
});
|
|
45
37
|
// Main checkout query
|
|
46
38
|
const { data: checkout, isLoading, error, isSuccess, refetch, } = useQuery({
|
|
47
39
|
queryKey: ['checkout', checkoutToken, currency.code],
|
|
@@ -50,12 +42,6 @@ export function useCheckoutQuery(options = {}) {
|
|
|
50
42
|
staleTime: 30000, // 30 seconds
|
|
51
43
|
refetchOnWindowFocus: false,
|
|
52
44
|
});
|
|
53
|
-
console.log('๐ [useCheckoutQuery] Query result:', {
|
|
54
|
-
hasCheckout: !!checkout,
|
|
55
|
-
isLoading,
|
|
56
|
-
error: error?.message,
|
|
57
|
-
isSuccess
|
|
58
|
-
});
|
|
59
45
|
// Refresh function
|
|
60
46
|
const refresh = useCallback(async () => {
|
|
61
47
|
if (checkoutToken) {
|
|
@@ -8,7 +8,7 @@ export function useCheckoutToken(options = {}) {
|
|
|
8
8
|
const { isSessionInitialized } = useTagadaContext();
|
|
9
9
|
const { checkoutToken: providedToken, autoLoadFromToken = true } = options;
|
|
10
10
|
const [checkoutToken, setCheckoutToken] = useState(providedToken || null);
|
|
11
|
-
const [isLoading,
|
|
11
|
+
const [isLoading, _setIsLoading] = useState(false);
|
|
12
12
|
const [error, setError] = useState(null);
|
|
13
13
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
14
14
|
const hasAutoLoadedRef = useRef(false);
|
|
@@ -20,10 +20,6 @@ export function useCheckoutToken(options = {}) {
|
|
|
20
20
|
// Update token when provided token changes
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
if (providedToken && providedToken !== checkoutToken) {
|
|
23
|
-
console.log('๐ง useCheckoutToken: Token updated from props', {
|
|
24
|
-
oldToken: checkoutToken ? checkoutToken.substring(0, 8) + '...' : null,
|
|
25
|
-
newToken: providedToken.substring(0, 8) + '...'
|
|
26
|
-
});
|
|
27
23
|
setCheckoutToken(providedToken);
|
|
28
24
|
setIsInitialized(false);
|
|
29
25
|
hasAutoLoadedRef.current = false;
|
|
@@ -35,9 +31,6 @@ export function useCheckoutToken(options = {}) {
|
|
|
35
31
|
const urlParams = new URLSearchParams(window.location.search);
|
|
36
32
|
const urlToken = urlParams.get('checkoutToken') || urlParams.get('token');
|
|
37
33
|
if (urlToken) {
|
|
38
|
-
console.log('๐ง useCheckoutToken: Auto-loading token from URL', {
|
|
39
|
-
tokenPreview: urlToken.substring(0, 8) + '...'
|
|
40
|
-
});
|
|
41
34
|
setCheckoutToken(urlToken);
|
|
42
35
|
hasAutoLoadedRef.current = true;
|
|
43
36
|
}
|
|
@@ -48,22 +41,14 @@ export function useCheckoutToken(options = {}) {
|
|
|
48
41
|
if (!isSessionInitialized || !checkoutToken || isInitialized) {
|
|
49
42
|
return;
|
|
50
43
|
}
|
|
51
|
-
console.log('๐ง useCheckoutToken: Session initialized, token ready', {
|
|
52
|
-
tokenPreview: checkoutToken.substring(0, 8) + '...',
|
|
53
|
-
isSessionInitialized
|
|
54
|
-
});
|
|
55
44
|
setIsInitialized(true);
|
|
56
45
|
}, [isSessionInitialized, checkoutToken, isInitialized]);
|
|
57
46
|
const setToken = useCallback((token) => {
|
|
58
|
-
console.log('๐ง useCheckoutToken: Setting token', {
|
|
59
|
-
tokenPreview: token ? token.substring(0, 8) + '...' : null
|
|
60
|
-
});
|
|
61
47
|
setCheckoutToken(token);
|
|
62
48
|
setIsInitialized(!!token);
|
|
63
49
|
setError(null);
|
|
64
50
|
}, []);
|
|
65
51
|
const clearToken = useCallback(() => {
|
|
66
|
-
console.log('๐ง useCheckoutToken: Clearing token');
|
|
67
52
|
setCheckoutToken(null);
|
|
68
53
|
setIsInitialized(false);
|
|
69
54
|
setError(null);
|
|
@@ -80,18 +80,15 @@ export function useGeoLocation(options = {}) {
|
|
|
80
80
|
try {
|
|
81
81
|
const targetIp = customIp || ip;
|
|
82
82
|
const endpoint = targetIp ? `/api/v1/geo?ip=${encodeURIComponent(targetIp)}` : '/api/v1/geo';
|
|
83
|
-
console.log('[SDK] Fetching geolocation data for IP:', targetIp || 'client IP');
|
|
84
83
|
const geoData = await apiService.fetch(endpoint, {
|
|
85
84
|
method: 'GET',
|
|
86
85
|
skipAuth: true, // Geolocation endpoint doesn't require authentication
|
|
87
86
|
});
|
|
88
87
|
setData(geoData);
|
|
89
|
-
console.log('[SDK] Geolocation data fetched successfully:', geoData);
|
|
90
88
|
}
|
|
91
89
|
catch (err) {
|
|
92
90
|
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch geolocation data';
|
|
93
91
|
setError(errorMessage);
|
|
94
|
-
console.error('[SDK] Error fetching geolocation data:', err);
|
|
95
92
|
}
|
|
96
93
|
finally {
|
|
97
94
|
setIsLoading(false);
|
|
@@ -18,7 +18,6 @@ export function useGoogleAutocomplete(options) {
|
|
|
18
18
|
// Inject Google Maps script
|
|
19
19
|
useEffect(() => {
|
|
20
20
|
if (!apiKey) {
|
|
21
|
-
console.error('Google Maps API key is required');
|
|
22
21
|
return;
|
|
23
22
|
}
|
|
24
23
|
if (scriptLoadedRef.current || window.google?.maps) {
|
|
@@ -56,12 +55,11 @@ export function useGoogleAutocomplete(options) {
|
|
|
56
55
|
script.async = true;
|
|
57
56
|
script.defer = true;
|
|
58
57
|
script.onload = () => {
|
|
59
|
-
console.log('๐บ๏ธ Google Maps API loaded successfully');
|
|
60
58
|
setIsScriptLoaded(true);
|
|
61
59
|
scriptLoadedRef.current = true;
|
|
62
60
|
};
|
|
63
61
|
script.onerror = () => {
|
|
64
|
-
|
|
62
|
+
// Failed to load Google Maps API
|
|
65
63
|
};
|
|
66
64
|
document.head.appendChild(script);
|
|
67
65
|
// Cleanup function
|
|
@@ -90,11 +88,9 @@ export function useGoogleAutocomplete(options) {
|
|
|
90
88
|
// Search for place predictions
|
|
91
89
|
const searchPlaces = useCallback((input, countryRestriction) => {
|
|
92
90
|
if (!isScriptLoaded) {
|
|
93
|
-
console.warn('Google Maps API not yet loaded');
|
|
94
91
|
return;
|
|
95
92
|
}
|
|
96
93
|
if (!initializeServices()) {
|
|
97
|
-
console.error('Google Places services not available');
|
|
98
94
|
return;
|
|
99
95
|
}
|
|
100
96
|
if (input.length < 3) {
|
|
@@ -112,12 +108,11 @@ export function useGoogleAutocomplete(options) {
|
|
|
112
108
|
setIsLoading(false);
|
|
113
109
|
if (status === window.google.maps.places.PlacesServiceStatus.OK && results) {
|
|
114
110
|
setPredictions(results);
|
|
115
|
-
console.log(`๐ Found ${results.length} predictions for "${input}"`);
|
|
116
111
|
}
|
|
117
112
|
else {
|
|
118
113
|
setPredictions([]);
|
|
119
114
|
if (status !== window.google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
|
|
120
|
-
|
|
115
|
+
// Prediction failed but not zero results
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
});
|
|
@@ -126,12 +121,10 @@ export function useGoogleAutocomplete(options) {
|
|
|
126
121
|
const getPlaceDetails = useCallback((placeId) => {
|
|
127
122
|
return new Promise((resolve) => {
|
|
128
123
|
if (!isScriptLoaded) {
|
|
129
|
-
console.warn('Google Maps API not yet loaded');
|
|
130
124
|
resolve(null);
|
|
131
125
|
return;
|
|
132
126
|
}
|
|
133
127
|
if (!initializeServices()) {
|
|
134
|
-
console.error('Google Places services not available');
|
|
135
128
|
resolve(null);
|
|
136
129
|
return;
|
|
137
130
|
}
|
|
@@ -141,11 +134,9 @@ export function useGoogleAutocomplete(options) {
|
|
|
141
134
|
};
|
|
142
135
|
placesServiceRef.current.getDetails(request, (place, status) => {
|
|
143
136
|
if (status === window.google.maps.places.PlacesServiceStatus.OK && place) {
|
|
144
|
-
console.log('๐ Got place details:', place);
|
|
145
137
|
resolve(place);
|
|
146
138
|
}
|
|
147
139
|
else {
|
|
148
|
-
console.error('Failed to get place details:', status);
|
|
149
140
|
resolve(null);
|
|
150
141
|
}
|
|
151
142
|
});
|
|
@@ -162,7 +153,6 @@ export function useGoogleAutocomplete(options) {
|
|
|
162
153
|
country: '',
|
|
163
154
|
postalCode: '',
|
|
164
155
|
};
|
|
165
|
-
console.log('๐๏ธ Extracted address components:', place.address_components);
|
|
166
156
|
place.address_components?.forEach((component) => {
|
|
167
157
|
const types = component.types;
|
|
168
158
|
if (types.includes('street_number')) {
|
|
@@ -188,7 +178,6 @@ export function useGoogleAutocomplete(options) {
|
|
|
188
178
|
extracted.postalCode = component.long_name;
|
|
189
179
|
}
|
|
190
180
|
});
|
|
191
|
-
console.log('๐๏ธ Extracted address components:', extracted);
|
|
192
181
|
return extracted;
|
|
193
182
|
}, []);
|
|
194
183
|
// Clear predictions
|
|
@@ -20,7 +20,6 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
20
20
|
setRegisteredLanguages(getRegisteredLanguages());
|
|
21
21
|
})
|
|
22
22
|
.catch((error) => {
|
|
23
|
-
console.error(`Failed to auto-import language ${language}:`, error);
|
|
24
23
|
});
|
|
25
24
|
}
|
|
26
25
|
}, [language, autoImport]);
|
|
@@ -86,8 +85,8 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
86
85
|
registeredLanguages,
|
|
87
86
|
};
|
|
88
87
|
}
|
|
89
|
-
catch (
|
|
90
|
-
|
|
88
|
+
catch (_error) {
|
|
89
|
+
// Error handling removed
|
|
91
90
|
return {
|
|
92
91
|
countries: {},
|
|
93
92
|
getRegions: () => [],
|
|
@@ -62,7 +62,6 @@ export function useOffersQuery(options = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
|
-
console.error(`[SDK] Failed to fetch order summary for offer ${offerId}:`, error);
|
|
66
65
|
setCheckoutSessions(prev => ({
|
|
67
66
|
...prev,
|
|
68
67
|
[offerId]: {
|
|
@@ -100,40 +99,40 @@ export function useOffersQuery(options = {}) {
|
|
|
100
99
|
mutationFn: async ({ offerId, returnUrl }) => {
|
|
101
100
|
return await offersResource.createCheckoutSession(offerId, returnUrl);
|
|
102
101
|
},
|
|
103
|
-
onError: (
|
|
104
|
-
|
|
102
|
+
onError: (_error) => {
|
|
103
|
+
// Error handling removed
|
|
105
104
|
},
|
|
106
105
|
});
|
|
107
106
|
const payOfferMutation = useMutation({
|
|
108
107
|
mutationFn: async ({ offerId, orderId }) => {
|
|
109
108
|
return await offersResource.payOffer(offerId, orderId);
|
|
110
109
|
},
|
|
111
|
-
onError: (
|
|
112
|
-
|
|
110
|
+
onError: (_error) => {
|
|
111
|
+
// Error handling removed
|
|
113
112
|
},
|
|
114
113
|
});
|
|
115
114
|
const transformToCheckoutMutation = useMutation({
|
|
116
115
|
mutationFn: async ({ offerId, returnUrl }) => {
|
|
117
116
|
return await offersResource.transformToCheckout(offerId, returnUrl);
|
|
118
117
|
},
|
|
119
|
-
onError: (
|
|
120
|
-
|
|
118
|
+
onError: (_error) => {
|
|
119
|
+
// Error handling removed
|
|
121
120
|
},
|
|
122
121
|
});
|
|
123
122
|
const payWithCheckoutSessionMutation = useMutation({
|
|
124
123
|
mutationFn: async ({ checkoutSessionId, orderId }) => {
|
|
125
124
|
return await offersResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
126
125
|
},
|
|
127
|
-
onError: (
|
|
128
|
-
|
|
126
|
+
onError: (_error) => {
|
|
127
|
+
// Error handling removed
|
|
129
128
|
},
|
|
130
129
|
});
|
|
131
130
|
const initCheckoutSessionMutation = useMutation({
|
|
132
131
|
mutationFn: async ({ offerId, orderId, customerId }) => {
|
|
133
132
|
return await offersResource.initCheckoutSession(offerId, orderId, customerId);
|
|
134
133
|
},
|
|
135
|
-
onError: (
|
|
136
|
-
|
|
134
|
+
onError: (_error) => {
|
|
135
|
+
// Error handling removed
|
|
137
136
|
},
|
|
138
137
|
});
|
|
139
138
|
// Helper functions (matching V1 useOffers exactly)
|
|
@@ -208,36 +207,30 @@ export function useOffersQuery(options = {}) {
|
|
|
208
207
|
return checkoutSessions[offerId] || null;
|
|
209
208
|
},
|
|
210
209
|
initializeOfferCheckout: async (offerId) => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
215
|
-
}
|
|
216
|
-
// Initialize checkout session using transformToCheckout
|
|
217
|
-
const initResult = await offersResource.transformToCheckout(offerId, returnUrl);
|
|
218
|
-
if (!initResult.checkoutUrl) {
|
|
219
|
-
throw new Error('Failed to initialize checkout session');
|
|
220
|
-
}
|
|
221
|
-
// Extract session ID from checkout URL or use a placeholder
|
|
222
|
-
const sessionId = 'session_' + offerId; // This would need to be extracted from the actual response
|
|
223
|
-
// Initialize session state
|
|
224
|
-
setCheckoutSessions(prev => ({
|
|
225
|
-
...prev,
|
|
226
|
-
[offerId]: {
|
|
227
|
-
checkoutSessionId: sessionId,
|
|
228
|
-
orderSummary: null,
|
|
229
|
-
selectedVariants: {},
|
|
230
|
-
loadingVariants: {},
|
|
231
|
-
isUpdatingSummary: false,
|
|
232
|
-
}
|
|
233
|
-
}));
|
|
234
|
-
// Fetch order summary with variant options
|
|
235
|
-
await fetchOrderSummary(offerId, sessionId);
|
|
210
|
+
// Check if customer ID is available
|
|
211
|
+
if (!session?.customerId) {
|
|
212
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
236
213
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
214
|
+
// Initialize checkout session using transformToCheckout
|
|
215
|
+
const initResult = await offersResource.transformToCheckout(offerId, returnUrl);
|
|
216
|
+
if (!initResult.checkoutUrl) {
|
|
217
|
+
throw new Error('Failed to initialize checkout session');
|
|
240
218
|
}
|
|
219
|
+
// Extract session ID from checkout URL or use a placeholder
|
|
220
|
+
const sessionId = 'session_' + offerId; // This would need to be extracted from the actual response
|
|
221
|
+
// Initialize session state
|
|
222
|
+
setCheckoutSessions(prev => ({
|
|
223
|
+
...prev,
|
|
224
|
+
[offerId]: {
|
|
225
|
+
checkoutSessionId: sessionId,
|
|
226
|
+
orderSummary: null,
|
|
227
|
+
selectedVariants: {},
|
|
228
|
+
loadingVariants: {},
|
|
229
|
+
isUpdatingSummary: false,
|
|
230
|
+
}
|
|
231
|
+
}));
|
|
232
|
+
// Fetch order summary with variant options
|
|
233
|
+
await fetchOrderSummary(offerId, sessionId);
|
|
241
234
|
},
|
|
242
235
|
getAvailableVariants: (offerId, productId) => {
|
|
243
236
|
const sessionState = checkoutSessions[offerId];
|
|
@@ -314,10 +307,6 @@ export function useOffersQuery(options = {}) {
|
|
|
314
307
|
// Refetch order summary after successful line item update
|
|
315
308
|
await fetchOrderSummary(offerId, sessionId);
|
|
316
309
|
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
console.error(`[SDK] Failed to update variant for offer ${offerId}:`, error);
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
321
310
|
finally {
|
|
322
311
|
// Clear loading state for this specific variant
|
|
323
312
|
setCheckoutSessions(prev => ({
|
|
@@ -12,10 +12,12 @@ export function useOrderBumpQuery(options) {
|
|
|
12
12
|
// Use checkout query only if no checkout is provided
|
|
13
13
|
const { checkout: loadedCheckout } = useCheckoutQuery({
|
|
14
14
|
checkoutToken: checkoutToken || undefined,
|
|
15
|
-
enabled: !providedCheckout
|
|
15
|
+
enabled: !providedCheckout, // Enable if no checkout is provided, regardless of token
|
|
16
16
|
});
|
|
17
17
|
// Use provided checkout or loaded checkout
|
|
18
18
|
const checkout = providedCheckout || loadedCheckout;
|
|
19
|
+
// Get the actual checkout token from the checkout session
|
|
20
|
+
const actualCheckoutToken = checkoutToken || checkout?.checkoutSession?.checkoutToken || null;
|
|
19
21
|
// Function to check if order bump is selected
|
|
20
22
|
const checkOrderBumpSelection = useCallback(() => {
|
|
21
23
|
if (!checkout?.checkoutSession?.sessionLineItems) {
|
|
@@ -44,18 +46,21 @@ export function useOrderBumpQuery(options) {
|
|
|
44
46
|
selected,
|
|
45
47
|
});
|
|
46
48
|
}, {
|
|
47
|
-
onMutate:
|
|
49
|
+
onMutate: ({ selected }) => {
|
|
48
50
|
// Optimistic update
|
|
49
51
|
setIsSelected(selected);
|
|
50
52
|
setError(null);
|
|
51
53
|
},
|
|
52
54
|
onSuccess: () => {
|
|
53
55
|
// Invalidate related queries
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
// Use the actual checkout token (from parameter or checkout session)
|
|
57
|
+
const tokenToUse = checkoutToken || checkout?.checkoutSession?.checkoutToken;
|
|
58
|
+
if (tokenToUse) {
|
|
59
|
+
invalidateCheckout(String(tokenToUse));
|
|
56
60
|
}
|
|
61
|
+
// Always invalidate promotions based on checkout session ID
|
|
57
62
|
if (checkout?.checkoutSession?.id) {
|
|
58
|
-
invalidatePromotions(checkout.checkoutSession.id);
|
|
63
|
+
invalidatePromotions(String(checkout.checkoutSession.id));
|
|
59
64
|
}
|
|
60
65
|
},
|
|
61
66
|
onError: (err, { selected }) => {
|
|
@@ -66,19 +71,18 @@ export function useOrderBumpQuery(options) {
|
|
|
66
71
|
});
|
|
67
72
|
const toggle = useCallback(async (selected) => {
|
|
68
73
|
if (!checkout?.checkoutSession?.id) {
|
|
69
|
-
console.warn('useOrderBumpQuery: No checkout session available yet');
|
|
70
74
|
return { success: false, error: 'Checkout session not ready' };
|
|
71
75
|
}
|
|
72
76
|
const targetState = selected ?? !isSelected;
|
|
73
77
|
try {
|
|
74
|
-
|
|
78
|
+
await toggleMutation.mutateAsync({ selected: targetState });
|
|
75
79
|
return { success: true };
|
|
76
80
|
}
|
|
77
81
|
catch (err) {
|
|
78
82
|
const error = err instanceof Error ? err : new Error('Failed to toggle order bump');
|
|
79
83
|
return { success: false, error: error.message };
|
|
80
84
|
}
|
|
81
|
-
}, [checkout?.checkoutSession?.id, isSelected, toggleMutation]);
|
|
85
|
+
}, [checkout?.checkoutSession?.id, isSelected, toggleMutation, offerId, productId, checkoutToken, actualCheckoutToken]);
|
|
82
86
|
return {
|
|
83
87
|
isSelected,
|
|
84
88
|
isToggling: toggleMutation.isPending,
|
|
@@ -32,24 +32,21 @@ export function usePaymentPolling() {
|
|
|
32
32
|
isPollingRef.current = false;
|
|
33
33
|
currentPaymentIdRef.current = null;
|
|
34
34
|
if (isMountedRef.current) {
|
|
35
|
-
|
|
35
|
+
// Component is still mounted
|
|
36
36
|
}
|
|
37
37
|
}, []);
|
|
38
38
|
const startPolling = useCallback((paymentId, options = {}) => {
|
|
39
39
|
if (!paymentId) {
|
|
40
|
-
console.error('Cannot poll payment status: paymentId is missing');
|
|
41
40
|
return { stop: stopPolling, isPolling: () => false };
|
|
42
41
|
}
|
|
43
42
|
// Prevent multiple polling sessions for the same payment
|
|
44
43
|
if (currentPaymentIdRef.current === paymentId && isPollingRef.current) {
|
|
45
|
-
console.log('Already polling for payment:', paymentId);
|
|
46
44
|
return { stop: stopPolling, isPolling: () => isPollingRef.current };
|
|
47
45
|
}
|
|
48
46
|
// Clean up any existing polling
|
|
49
47
|
stopPolling();
|
|
50
48
|
// Don't start polling if component is unmounted
|
|
51
49
|
if (!isMountedRef.current) {
|
|
52
|
-
console.log('Component unmounted, skipping polling start');
|
|
53
50
|
return { stop: stopPolling, isPolling: () => false };
|
|
54
51
|
}
|
|
55
52
|
// Reset attempts counter and set current payment
|
|
@@ -57,7 +54,6 @@ export function usePaymentPolling() {
|
|
|
57
54
|
isPollingRef.current = true;
|
|
58
55
|
currentPaymentIdRef.current = paymentId;
|
|
59
56
|
const { onRequireAction, onSuccess, onFailure, maxAttempts = 20, pollInterval = 1500 } = options;
|
|
60
|
-
console.log('Starting to poll payment status for:', paymentId);
|
|
61
57
|
const checkPaymentStatus = async () => {
|
|
62
58
|
// Stop if component was unmounted or polling was stopped
|
|
63
59
|
if (!isMountedRef.current || !isPollingRef.current) {
|
|
@@ -65,7 +61,6 @@ export function usePaymentPolling() {
|
|
|
65
61
|
}
|
|
66
62
|
try {
|
|
67
63
|
attemptsRef.current++;
|
|
68
|
-
console.log(`Polling attempt ${attemptsRef.current}/${maxAttempts} for payment ${paymentId}`);
|
|
69
64
|
const payment = await paymentsResource.getPaymentStatus(paymentId);
|
|
70
65
|
// Check again after async operation
|
|
71
66
|
if (!isMountedRef.current || !isPollingRef.current) {
|
|
@@ -73,13 +68,10 @@ export function usePaymentPolling() {
|
|
|
73
68
|
}
|
|
74
69
|
// Type guard and validation
|
|
75
70
|
if (!payment?.id) {
|
|
76
|
-
console.warn('Invalid payment response received');
|
|
77
71
|
return;
|
|
78
72
|
}
|
|
79
|
-
console.log('Payment status update:', payment);
|
|
80
73
|
// Check if payment requires action
|
|
81
74
|
if (payment.requireAction !== 'none' && payment.requireActionData) {
|
|
82
|
-
console.log('Payment requires new action, handling...');
|
|
83
75
|
stopPolling();
|
|
84
76
|
if (isMountedRef.current && onRequireAction) {
|
|
85
77
|
onRequireAction(payment, stopPolling);
|
|
@@ -89,7 +81,6 @@ export function usePaymentPolling() {
|
|
|
89
81
|
// Check for successful payment
|
|
90
82
|
if (payment.status === 'succeeded' ||
|
|
91
83
|
(payment.status === 'pending' && payment.subStatus === 'authorized')) {
|
|
92
|
-
console.log('Payment succeeded, stopping polling');
|
|
93
84
|
stopPolling();
|
|
94
85
|
if (isMountedRef.current && onSuccess) {
|
|
95
86
|
onSuccess(payment);
|
|
@@ -98,7 +89,6 @@ export function usePaymentPolling() {
|
|
|
98
89
|
}
|
|
99
90
|
// Check for failed payment (non-succeeded and not pending)
|
|
100
91
|
if (payment.status !== 'succeeded' && payment.status !== 'pending') {
|
|
101
|
-
console.log('Payment failed, stopping polling');
|
|
102
92
|
stopPolling();
|
|
103
93
|
if (isMountedRef.current && onFailure) {
|
|
104
94
|
onFailure(payment.status || 'Payment failed');
|
|
@@ -107,18 +97,15 @@ export function usePaymentPolling() {
|
|
|
107
97
|
}
|
|
108
98
|
// Stop after max attempts
|
|
109
99
|
if (attemptsRef.current >= maxAttempts) {
|
|
110
|
-
console.log('Reached maximum polling attempts');
|
|
111
100
|
stopPolling();
|
|
112
101
|
if (isMountedRef.current && onFailure) {
|
|
113
102
|
onFailure('Payment verification timeout');
|
|
114
103
|
}
|
|
115
104
|
}
|
|
116
105
|
}
|
|
117
|
-
catch (
|
|
118
|
-
console.error('Error checking payment status:', error);
|
|
106
|
+
catch (_error) {
|
|
119
107
|
// Stop polling on repeated errors to prevent infinite loops
|
|
120
108
|
if (attemptsRef.current >= 3) {
|
|
121
|
-
console.log('Multiple errors encountered, stopping polling');
|
|
122
109
|
stopPolling();
|
|
123
110
|
if (isMountedRef.current && onFailure) {
|
|
124
111
|
onFailure('Payment verification failed due to network errors');
|
|
@@ -39,11 +39,9 @@ export function usePaymentQuery() {
|
|
|
39
39
|
// Handle BasisTheory initialization errors (only log once when state changes)
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
if (btError) {
|
|
42
|
-
console.error('BasisTheory initialization error:', btError);
|
|
43
42
|
setError('Failed to initialize payment processor: ' + btError.message);
|
|
44
43
|
}
|
|
45
44
|
else if (basisTheory && !error) {
|
|
46
|
-
console.log('โ
BasisTheory initialized successfully');
|
|
47
45
|
setError(null); // Clear any previous errors
|
|
48
46
|
}
|
|
49
47
|
}, [basisTheory, btError]); // Removed error from dependency to prevent loops
|
|
@@ -66,16 +64,14 @@ export function usePaymentQuery() {
|
|
|
66
64
|
try {
|
|
67
65
|
await paymentsResource.markPaymentActionProcessed(payment.id);
|
|
68
66
|
}
|
|
69
|
-
catch (
|
|
70
|
-
|
|
67
|
+
catch (_error) {
|
|
68
|
+
// Error handling removed
|
|
71
69
|
}
|
|
72
|
-
console.log('Processing payment action:', actionData.type);
|
|
73
70
|
switch (actionData.type) {
|
|
74
71
|
case 'threeds_auth':
|
|
75
72
|
if (actionData.metadata?.threedsSession && !challengeInProgressRef.current) {
|
|
76
73
|
try {
|
|
77
74
|
challengeInProgressRef.current = true;
|
|
78
|
-
console.log('Starting 3DS challenge...');
|
|
79
75
|
await startChallenge({
|
|
80
76
|
sessionId: actionData.metadata.threedsSession.externalSessionId,
|
|
81
77
|
acsChallengeUrl: actionData.metadata.threedsSession.acsChallengeUrl,
|
|
@@ -83,7 +79,6 @@ export function usePaymentQuery() {
|
|
|
83
79
|
threeDSVersion: actionData.metadata.threedsSession.messageVersion,
|
|
84
80
|
}, { provider: options.threedsProvider || 'basis_theory' });
|
|
85
81
|
challengeInProgressRef.current = false;
|
|
86
|
-
console.log('3DS challenge completed');
|
|
87
82
|
// Start polling after challenge completion
|
|
88
83
|
if (payment.id) {
|
|
89
84
|
startPolling(payment.id, {
|
|
@@ -98,7 +93,6 @@ export function usePaymentQuery() {
|
|
|
98
93
|
});
|
|
99
94
|
},
|
|
100
95
|
onFailure: (errorMsg) => {
|
|
101
|
-
console.error('Payment failed:', errorMsg);
|
|
102
96
|
setError(errorMsg);
|
|
103
97
|
setIsLoading(false);
|
|
104
98
|
options.onFailure?.(errorMsg);
|
|
@@ -106,10 +100,9 @@ export function usePaymentQuery() {
|
|
|
106
100
|
});
|
|
107
101
|
}
|
|
108
102
|
}
|
|
109
|
-
catch (
|
|
103
|
+
catch (_error) {
|
|
110
104
|
challengeInProgressRef.current = false;
|
|
111
|
-
|
|
112
|
-
const errorMsg = error instanceof Error ? error.message : 'Failed to start 3DS challenge';
|
|
105
|
+
const errorMsg = _error instanceof Error ? _error.message : 'Failed to start 3DS challenge';
|
|
113
106
|
setError(errorMsg);
|
|
114
107
|
setIsLoading(false);
|
|
115
108
|
options.onFailure?.(errorMsg);
|
|
@@ -148,7 +141,6 @@ export function usePaymentQuery() {
|
|
|
148
141
|
initiatedBy: options.initiatedBy,
|
|
149
142
|
source: options.source,
|
|
150
143
|
});
|
|
151
|
-
console.log('Payment response:', response);
|
|
152
144
|
setCurrentPaymentId(response.payment?.id);
|
|
153
145
|
if (response.payment.requireAction !== 'none') {
|
|
154
146
|
await handlePaymentAction(response.payment, options);
|
|
@@ -171,7 +163,6 @@ export function usePaymentQuery() {
|
|
|
171
163
|
});
|
|
172
164
|
},
|
|
173
165
|
onFailure: (errorMsg) => {
|
|
174
|
-
console.error('Payment failed:', errorMsg);
|
|
175
166
|
setError(errorMsg);
|
|
176
167
|
setIsLoading(false);
|
|
177
168
|
options.onFailure?.(errorMsg);
|
|
@@ -180,12 +171,12 @@ export function usePaymentQuery() {
|
|
|
180
171
|
}
|
|
181
172
|
return response;
|
|
182
173
|
}
|
|
183
|
-
catch (
|
|
184
|
-
const errorMsg =
|
|
174
|
+
catch (_error) {
|
|
175
|
+
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
185
176
|
setError(errorMsg);
|
|
186
177
|
setIsLoading(false);
|
|
187
178
|
options.onFailure?.(errorMsg);
|
|
188
|
-
throw
|
|
179
|
+
throw _error;
|
|
189
180
|
}
|
|
190
181
|
}, [paymentsResource, handlePaymentAction, startPolling]);
|
|
191
182
|
// Process card payment - matches old implementation
|
|
@@ -204,19 +195,18 @@ export function usePaymentQuery() {
|
|
|
204
195
|
});
|
|
205
196
|
threedsSessionId = threedsSession.id;
|
|
206
197
|
}
|
|
207
|
-
catch (
|
|
208
|
-
console.warn('Failed to create 3DS session, proceeding without 3DS:', error);
|
|
198
|
+
catch (_error) {
|
|
209
199
|
}
|
|
210
200
|
}
|
|
211
201
|
// 3. Process payment directly
|
|
212
202
|
return await processPaymentDirect(checkoutSessionId, paymentInstrument.id, threedsSessionId, options);
|
|
213
203
|
}
|
|
214
|
-
catch (
|
|
204
|
+
catch (_error) {
|
|
215
205
|
setIsLoading(false);
|
|
216
|
-
const errorMsg =
|
|
206
|
+
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
217
207
|
setError(errorMsg);
|
|
218
208
|
options.onFailure?.(errorMsg);
|
|
219
|
-
throw
|
|
209
|
+
throw _error;
|
|
220
210
|
}
|
|
221
211
|
}, [createCardPaymentInstrument, createSession, processPaymentDirect]);
|
|
222
212
|
// Process Apple Pay payment - matches old implementation
|
|
@@ -229,12 +219,12 @@ export function usePaymentQuery() {
|
|
|
229
219
|
// 2. Process payment directly (Apple Pay typically doesn't require 3DS)
|
|
230
220
|
return await processPaymentDirect(checkoutSessionId, paymentInstrument.id, undefined, options);
|
|
231
221
|
}
|
|
232
|
-
catch (
|
|
222
|
+
catch (_error) {
|
|
233
223
|
setIsLoading(false);
|
|
234
|
-
const errorMsg =
|
|
224
|
+
const errorMsg = _error instanceof Error ? _error.message : 'Apple Pay payment failed';
|
|
235
225
|
setError(errorMsg);
|
|
236
226
|
options.onFailure?.(errorMsg);
|
|
237
|
-
throw
|
|
227
|
+
throw _error;
|
|
238
228
|
}
|
|
239
229
|
}, [createApplePayPaymentInstrument, processPaymentDirect]);
|
|
240
230
|
// Process payment with existing instrument - matches old implementation
|
|
@@ -244,12 +234,12 @@ export function usePaymentQuery() {
|
|
|
244
234
|
try {
|
|
245
235
|
return await processPaymentDirect(checkoutSessionId, paymentInstrumentId, undefined, options);
|
|
246
236
|
}
|
|
247
|
-
catch (
|
|
237
|
+
catch (_error) {
|
|
248
238
|
setIsLoading(false);
|
|
249
|
-
const errorMsg =
|
|
239
|
+
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
250
240
|
setError(errorMsg);
|
|
251
241
|
options.onFailure?.(errorMsg);
|
|
252
|
-
throw
|
|
242
|
+
throw _error;
|
|
253
243
|
}
|
|
254
244
|
}, [processPaymentDirect]);
|
|
255
245
|
// Get card payment instruments - matches old implementation
|
|
@@ -259,7 +249,6 @@ export function usePaymentQuery() {
|
|
|
259
249
|
return response;
|
|
260
250
|
}
|
|
261
251
|
catch (error) {
|
|
262
|
-
console.error('Error fetching card payment instruments:', error);
|
|
263
252
|
const errorMsg = error instanceof Error ? error.message : 'Failed to fetch payment instruments';
|
|
264
253
|
setError(errorMsg);
|
|
265
254
|
throw error;
|
|
@@ -11,6 +11,8 @@ export interface UsePluginConfigResult<TConfig = Record<string, any>> {
|
|
|
11
11
|
storeId?: string;
|
|
12
12
|
accountId?: string;
|
|
13
13
|
basePath?: string;
|
|
14
|
+
loading: boolean;
|
|
15
|
+
error?: Error;
|
|
14
16
|
isValid: boolean;
|
|
15
17
|
}
|
|
16
18
|
export declare function usePluginConfig<TConfig = Record<string, any>>(options?: UsePluginConfigOptions): UsePluginConfigResult<TConfig>;
|
|
@@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
|
|
6
6
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
7
7
|
import { PluginConfigUtils } from '../../core/utils/pluginConfig';
|
|
8
8
|
export function usePluginConfig(options = {}) {
|
|
9
|
-
const { pluginConfig } = useTagadaContext();
|
|
9
|
+
const { pluginConfig, pluginConfigLoading } = useTagadaContext();
|
|
10
10
|
const config = useMemo(() => {
|
|
11
11
|
const baseConfig = PluginConfigUtils.getPluginConfig(options.rawConfig, {
|
|
12
12
|
storeId: pluginConfig.storeId,
|
|
@@ -25,12 +25,13 @@ export function usePluginConfig(options = {}) {
|
|
|
25
25
|
const isValid = useMemo(() => {
|
|
26
26
|
return PluginConfigUtils.validateConfig(config);
|
|
27
27
|
}, [config]);
|
|
28
|
-
console.log('๐ [usePluginConfig] Config:', config);
|
|
29
28
|
return {
|
|
30
29
|
config,
|
|
31
30
|
storeId: config.storeId,
|
|
32
31
|
accountId: config.accountId,
|
|
33
32
|
basePath: config.basePath,
|
|
33
|
+
loading: pluginConfigLoading,
|
|
34
|
+
error: undefined, // TODO: Add error handling if needed
|
|
34
35
|
isValid,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -60,7 +60,6 @@ export function usePostPurchasesQuery(options) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
console.error(`[SDK] Failed to fetch order summary for offer ${offerId}:`, error);
|
|
64
63
|
setCheckoutSessions(prev => ({
|
|
65
64
|
...prev,
|
|
66
65
|
[offerId]: {
|
|
@@ -86,10 +85,9 @@ export function usePostPurchasesQuery(options) {
|
|
|
86
85
|
offers.forEach((offer) => {
|
|
87
86
|
try {
|
|
88
87
|
// This is a placeholder - in a full implementation, you'd call initializeOfferCheckout
|
|
89
|
-
console.log(`Auto-initializing checkout for offer: ${offer.id}`);
|
|
90
88
|
}
|
|
91
|
-
catch (
|
|
92
|
-
|
|
89
|
+
catch (_error) {
|
|
90
|
+
// Error handling removed
|
|
93
91
|
}
|
|
94
92
|
});
|
|
95
93
|
}
|
|
@@ -169,36 +167,18 @@ export function usePostPurchasesQuery(options) {
|
|
|
169
167
|
refresh: () => void refetch(),
|
|
170
168
|
// V1 compatibility methods
|
|
171
169
|
payWithCheckoutSession: async (checkoutSessionId, orderId) => {
|
|
172
|
-
|
|
173
|
-
await postPurchasesResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
console.error('Failed to pay with checkout session:', err);
|
|
177
|
-
throw err;
|
|
178
|
-
}
|
|
170
|
+
await postPurchasesResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
179
171
|
},
|
|
180
172
|
// Missing V1 methods implementations
|
|
181
173
|
initCheckoutSession: async (offerId, orderId) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
186
|
-
}
|
|
187
|
-
return await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
188
|
-
}
|
|
189
|
-
catch (err) {
|
|
190
|
-
console.error('Failed to initialize checkout session:', err);
|
|
191
|
-
throw err;
|
|
174
|
+
// Check if customer ID is available
|
|
175
|
+
if (!session?.customerId) {
|
|
176
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
192
177
|
}
|
|
178
|
+
return await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
193
179
|
},
|
|
194
180
|
initCheckoutSessionWithVariants: async (offerId, orderId, lineItems) => {
|
|
195
|
-
|
|
196
|
-
return await postPurchasesResource.initCheckoutSessionWithVariants(offerId, orderId, lineItems);
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
console.error('Failed to initialize checkout session with variants:', err);
|
|
200
|
-
throw err;
|
|
201
|
-
}
|
|
181
|
+
return await postPurchasesResource.initCheckoutSessionWithVariants(offerId, orderId, lineItems);
|
|
202
182
|
},
|
|
203
183
|
getOffer: (offerId) => {
|
|
204
184
|
return offers.find(offer => offer.id === offerId);
|
|
@@ -247,9 +227,8 @@ export function usePostPurchasesQuery(options) {
|
|
|
247
227
|
// Fetch order summary with variant options
|
|
248
228
|
await fetchOrderSummary(offerId, sessionId);
|
|
249
229
|
}
|
|
250
|
-
catch (
|
|
251
|
-
|
|
252
|
-
throw error;
|
|
230
|
+
catch (_error) {
|
|
231
|
+
throw _error;
|
|
253
232
|
}
|
|
254
233
|
},
|
|
255
234
|
getAvailableVariants: (offerId, productId) => {
|
|
@@ -326,9 +305,8 @@ export function usePostPurchasesQuery(options) {
|
|
|
326
305
|
// Refetch order summary after successful line item update
|
|
327
306
|
await fetchOrderSummary(offerId, sessionState.checkoutSessionId);
|
|
328
307
|
}
|
|
329
|
-
catch (
|
|
330
|
-
|
|
331
|
-
throw error;
|
|
308
|
+
catch (_error) {
|
|
309
|
+
throw _error;
|
|
332
310
|
}
|
|
333
311
|
finally {
|
|
334
312
|
// Clear loading state for this specific variant
|
|
@@ -28,11 +28,9 @@ export function useThreeds(options = {}) {
|
|
|
28
28
|
const { BasisTheory3ds } = await import('@basis-theory/web-threeds');
|
|
29
29
|
if (isMounted) {
|
|
30
30
|
setBasisTheory3ds(() => BasisTheory3ds);
|
|
31
|
-
console.log('โ
BasisTheory3ds initialized successfully');
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
catch (err) {
|
|
35
|
-
console.error('Failed to load BasisTheory3ds:', err);
|
|
36
34
|
if (isMounted) {
|
|
37
35
|
setError(err instanceof Error ? err : new Error('Failed to load 3DS library'));
|
|
38
36
|
}
|
|
@@ -45,29 +43,22 @@ export function useThreeds(options = {}) {
|
|
|
45
43
|
}, []);
|
|
46
44
|
// Create a 3DS session with BasisTheory
|
|
47
45
|
const createBasisTheorySession = useCallback(async (paymentInstrument) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw new Error('BasisTheory3ds not loaded yet');
|
|
51
|
-
}
|
|
52
|
-
// Use the same API key approach as the working CMS version
|
|
53
|
-
const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
|
|
54
|
-
const bt3ds = basisTheory3ds(apiKey);
|
|
55
|
-
console.log('paymentInstrument paymentInstrument', paymentInstrument?.token);
|
|
56
|
-
const session = await bt3ds.createSession({
|
|
57
|
-
tokenId: paymentInstrument.token,
|
|
58
|
-
});
|
|
59
|
-
// Create session through our API
|
|
60
|
-
const threedsSession = await threedsResource.createSession({
|
|
61
|
-
provider: 'basis_theory',
|
|
62
|
-
sessionData: session,
|
|
63
|
-
paymentInstrumentId: paymentInstrument.id,
|
|
64
|
-
});
|
|
65
|
-
return threedsSession;
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
console.error('Error creating BasisTheory 3DS session:', error);
|
|
69
|
-
throw error;
|
|
46
|
+
if (!basisTheory3ds) {
|
|
47
|
+
throw new Error('BasisTheory3ds not loaded yet');
|
|
70
48
|
}
|
|
49
|
+
// Use the same API key approach as the working CMS version
|
|
50
|
+
const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
|
|
51
|
+
const bt3ds = basisTheory3ds(apiKey);
|
|
52
|
+
const session = await bt3ds.createSession({
|
|
53
|
+
tokenId: paymentInstrument.token,
|
|
54
|
+
});
|
|
55
|
+
// Create session through our API
|
|
56
|
+
const threedsSession = await threedsResource.createSession({
|
|
57
|
+
provider: 'basis_theory',
|
|
58
|
+
sessionData: session,
|
|
59
|
+
paymentInstrumentId: paymentInstrument.id,
|
|
60
|
+
});
|
|
61
|
+
return threedsSession;
|
|
71
62
|
}, [threedsResource, basisTheory3ds]);
|
|
72
63
|
// Generic createSession method that supports multiple providers
|
|
73
64
|
const createSession = useCallback(async (paymentInstrument, options) => {
|
|
@@ -111,12 +102,6 @@ export function useThreeds(options = {}) {
|
|
|
111
102
|
},
|
|
112
103
|
});
|
|
113
104
|
const bt3ds = basisTheory3ds(apiKey);
|
|
114
|
-
console.log('bt3ds starting challenge with params', {
|
|
115
|
-
sessionId,
|
|
116
|
-
acsChallengeUrl,
|
|
117
|
-
acsTransactionId,
|
|
118
|
-
threeDSVersion,
|
|
119
|
-
});
|
|
120
105
|
const challengeCompletion = await bt3ds.startChallenge({
|
|
121
106
|
sessionId,
|
|
122
107
|
acsChallengeUrl,
|
|
@@ -130,7 +115,6 @@ export function useThreeds(options = {}) {
|
|
|
130
115
|
return challengeCompletion;
|
|
131
116
|
}
|
|
132
117
|
catch (error) {
|
|
133
|
-
console.error('Error starting BasisTheory 3DS challenge:', error);
|
|
134
118
|
closeThreedsModal();
|
|
135
119
|
throw error;
|
|
136
120
|
}
|
|
@@ -48,9 +48,39 @@ const InitializationLoader = () => (_jsxs("div", { style: {
|
|
|
48
48
|
}
|
|
49
49
|
` })] }));
|
|
50
50
|
const TagadaContext = createContext(null);
|
|
51
|
+
// Global instance tracking for TagadaProvider
|
|
52
|
+
let globalTagadaInstance = null;
|
|
53
|
+
let globalTagadaInitialized = false;
|
|
51
54
|
export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
|
|
52
55
|
localConfig, blockUntilSessionReady = false, // Default to new non-blocking behavior
|
|
53
56
|
rawPluginConfig, }) {
|
|
57
|
+
// Instance tracking
|
|
58
|
+
const [instanceId] = useState(() => {
|
|
59
|
+
if (!globalTagadaInstance) {
|
|
60
|
+
globalTagadaInstance = Math.random().toString(36).substr(2, 9);
|
|
61
|
+
}
|
|
62
|
+
return globalTagadaInstance;
|
|
63
|
+
});
|
|
64
|
+
const isActiveInstance = useMemo(() => {
|
|
65
|
+
if (!globalTagadaInitialized) {
|
|
66
|
+
globalTagadaInitialized = true;
|
|
67
|
+
console.log(`โ
[TagadaProvider] Instance ${instanceId} is now the active instance`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is duplicate - blocking execution`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}, [instanceId]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
return () => {
|
|
77
|
+
if (globalTagadaInstance === instanceId) {
|
|
78
|
+
globalTagadaInitialized = false;
|
|
79
|
+
globalTagadaInstance = null;
|
|
80
|
+
console.log(`๐งน [TagadaProvider] Instance ${instanceId} cleanup - allowing new instances`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, [instanceId]);
|
|
54
84
|
// LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
|
|
55
85
|
const isLocalDev = typeof window !== 'undefined' &&
|
|
56
86
|
(window.location.hostname === 'localhost' ||
|
|
@@ -60,7 +90,7 @@ rawPluginConfig, }) {
|
|
|
60
90
|
// Debug logging (only log once during initial render)
|
|
61
91
|
const hasLoggedRef = useRef(false);
|
|
62
92
|
if (!hasLoggedRef.current) {
|
|
63
|
-
console.log(
|
|
93
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} Config Debug:`, {
|
|
64
94
|
hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
|
|
65
95
|
isLocalDev,
|
|
66
96
|
localConfig,
|
|
@@ -84,6 +114,11 @@ rawPluginConfig, }) {
|
|
|
84
114
|
const [configLoading, setConfigLoading] = useState(!rawPluginConfig);
|
|
85
115
|
// Load plugin config on mount with the specified variant
|
|
86
116
|
useEffect(() => {
|
|
117
|
+
// Prevent multiple config loads
|
|
118
|
+
if (configLoading === false && pluginConfig.storeId) {
|
|
119
|
+
console.log('๐ [TagadaProvider] Config already loaded, skipping reload');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
87
122
|
const loadConfig = async () => {
|
|
88
123
|
try {
|
|
89
124
|
// Use the v2 core loadPluginConfig function
|
|
@@ -239,12 +274,21 @@ rawPluginConfig, }) {
|
|
|
239
274
|
});
|
|
240
275
|
// Initialize session
|
|
241
276
|
const initializeSession = useCallback(async (sessionData) => {
|
|
277
|
+
if (!isActiveInstance) {
|
|
278
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping session initialization`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
242
281
|
if (!sessionData.storeId || !sessionData.accountId) {
|
|
243
|
-
console.error(
|
|
282
|
+
console.error(`[TagadaProvider] Instance ${instanceId} missing required session data`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Prevent multiple session initializations
|
|
286
|
+
if (isSessionInitialized) {
|
|
287
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} session already initialized, skipping`);
|
|
244
288
|
return;
|
|
245
289
|
}
|
|
246
290
|
if (finalDebugMode) {
|
|
247
|
-
console.debug(
|
|
291
|
+
console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Initializing session with store config...`, sessionData);
|
|
248
292
|
}
|
|
249
293
|
setIsLoading(true);
|
|
250
294
|
try {
|
|
@@ -352,15 +396,23 @@ rawPluginConfig, }) {
|
|
|
352
396
|
setIsInitialized(true);
|
|
353
397
|
setIsLoading(false);
|
|
354
398
|
}
|
|
355
|
-
}, [apiService, finalDebugMode]);
|
|
399
|
+
}, [apiService, finalDebugMode, isActiveInstance, instanceId]);
|
|
356
400
|
// Create anonymous token if needed
|
|
357
401
|
const createAnonymousToken = useCallback(async (targetStoreId) => {
|
|
402
|
+
if (!isActiveInstance) {
|
|
403
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping anonymous token creation`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
358
406
|
if (hasAttemptedAnonymousToken || !targetStoreId) {
|
|
407
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} anonymous token already attempted or no storeId:`, {
|
|
408
|
+
hasAttemptedAnonymousToken,
|
|
409
|
+
targetStoreId,
|
|
410
|
+
});
|
|
359
411
|
return;
|
|
360
412
|
}
|
|
361
|
-
console.log(
|
|
413
|
+
console.log(`[TagadaProvider] Instance ${instanceId} ๐ Starting Phase 3 - Session initialization...`);
|
|
362
414
|
if (finalDebugMode) {
|
|
363
|
-
console.debug(
|
|
415
|
+
console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Creating anonymous token for store:`, targetStoreId);
|
|
364
416
|
}
|
|
365
417
|
setHasAttemptedAnonymousToken(true);
|
|
366
418
|
try {
|
|
@@ -405,15 +457,24 @@ rawPluginConfig, }) {
|
|
|
405
457
|
setIsInitialized(true);
|
|
406
458
|
setIsLoading(false);
|
|
407
459
|
}
|
|
408
|
-
}, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode]);
|
|
460
|
+
}, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode, isActiveInstance, instanceId]);
|
|
409
461
|
// Initialize token from storage or create anonymous token
|
|
410
462
|
// This runs in the background after phases 1 & 2 complete, but doesn't block rendering
|
|
411
463
|
useEffect(() => {
|
|
464
|
+
if (!isActiveInstance) {
|
|
465
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping token initialization`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
412
468
|
if (isInitializing.current) {
|
|
469
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} already initializing, skipping`);
|
|
413
470
|
return;
|
|
414
471
|
}
|
|
415
472
|
// Wait for plugin config to load AND ensure we have a store ID before initializing
|
|
416
473
|
if (configLoading || !storeId) {
|
|
474
|
+
console.log(`โณ [TagadaProvider] Instance ${instanceId} waiting for config or storeId:`, {
|
|
475
|
+
configLoading,
|
|
476
|
+
storeId,
|
|
477
|
+
});
|
|
417
478
|
return;
|
|
418
479
|
}
|
|
419
480
|
isInitializing.current = true;
|
|
@@ -469,7 +530,7 @@ rawPluginConfig, }) {
|
|
|
469
530
|
}
|
|
470
531
|
};
|
|
471
532
|
void initializeToken();
|
|
472
|
-
}, [storeId, createAnonymousToken, initializeSession, configLoading]);
|
|
533
|
+
}, [storeId, createAnonymousToken, initializeSession, configLoading, isActiveInstance, instanceId]);
|
|
473
534
|
// Update auth state when customer/session changes
|
|
474
535
|
useEffect(() => {
|
|
475
536
|
setAuth({
|