@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.
@@ -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, setIsLoading] = useState(false);
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
- console.error('Failed to load Google Maps API');
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
- console.warn('Google Places prediction failed:', status);
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 (error) {
90
- console.error(`Failed to load ISO data for language: ${language}`, error);
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: (error) => {
104
- console.error('Failed to create checkout session:', error);
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: (error) => {
112
- console.error('Failed to pay offer:', error);
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: (error) => {
120
- console.error('Failed to transform offer to checkout:', error);
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: (error) => {
128
- console.error('Failed to pay with checkout session:', error);
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: (error) => {
136
- console.error('Failed to initialize checkout session:', error);
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
- try {
212
- // Check if customer ID is available
213
- if (!session?.customerId) {
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
- catch (error) {
238
- console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
239
- throw error;
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 && !!checkoutToken,
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: async ({ selected }) => {
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
- if (checkoutToken) {
55
- invalidateCheckout(checkoutToken);
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
- const result = await toggleMutation.mutateAsync({ selected: targetState });
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
- console.log('Stopped polling payment status');
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 (error) {
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 (error) {
70
- console.error('Error setting payment action as processed', error);
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 (error) {
103
+ catch (_error) {
110
104
  challengeInProgressRef.current = false;
111
- console.error('Error starting 3DS challenge:', error);
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 (error) {
184
- const errorMsg = error instanceof Error ? error.message : 'Payment failed';
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 error;
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 (error) {
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 (error) {
204
+ catch (_error) {
215
205
  setIsLoading(false);
216
- const errorMsg = error instanceof Error ? error.message : 'Payment failed';
206
+ const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
217
207
  setError(errorMsg);
218
208
  options.onFailure?.(errorMsg);
219
- throw error;
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 (error) {
222
+ catch (_error) {
233
223
  setIsLoading(false);
234
- const errorMsg = error instanceof Error ? error.message : 'Apple Pay payment failed';
224
+ const errorMsg = _error instanceof Error ? _error.message : 'Apple Pay payment failed';
235
225
  setError(errorMsg);
236
226
  options.onFailure?.(errorMsg);
237
- throw error;
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 (error) {
237
+ catch (_error) {
248
238
  setIsLoading(false);
249
- const errorMsg = error instanceof Error ? error.message : 'Payment failed';
239
+ const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
250
240
  setError(errorMsg);
251
241
  options.onFailure?.(errorMsg);
252
- throw error;
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 (error) {
92
- console.error(`Failed to auto-initialize checkout for offer ${offer.id}:`, error);
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
- try {
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
- try {
183
- // Check if customer ID is available
184
- if (!session?.customerId) {
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
- try {
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 (error) {
251
- console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
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 (error) {
330
- console.error(`[SDK] Failed to update variant for offer ${offerId}:`, error);
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
- try {
49
- if (!basisTheory3ds) {
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('๐Ÿ” TagadaProvider Config Debug:', {
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('[SDK] Missing required session data');
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('[SDK][DEBUG] Initializing session with store config...', sessionData);
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('[SDK] ๐Ÿš€ Starting Phase 3 - Session initialization...');
413
+ console.log(`[TagadaProvider] Instance ${instanceId} ๐Ÿš€ Starting Phase 3 - Session initialization...`);
362
414
  if (finalDebugMode) {
363
- console.debug('[SDK][DEBUG] Creating anonymous token for store:', targetStoreId);
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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",