@tagadapay/plugin-sdk 3.1.2 → 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +113 -113
  3. package/dist/external-tracker.js +1104 -491
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/hooks/useApplePay.js +25 -36
  7. package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
  8. package/dist/react/providers/TagadaProvider.js +5 -5
  9. package/dist/react/utils/money.d.ts +4 -3
  10. package/dist/react/utils/money.js +39 -6
  11. package/dist/react/utils/trackingUtils.js +1 -0
  12. package/dist/v2/core/client.js +34 -2
  13. package/dist/v2/core/config/environment.js +9 -2
  14. package/dist/v2/core/funnelClient.d.ts +92 -1
  15. package/dist/v2/core/funnelClient.js +247 -3
  16. package/dist/v2/core/resources/apiClient.js +1 -1
  17. package/dist/v2/core/resources/checkout.d.ts +68 -0
  18. package/dist/v2/core/resources/funnel.d.ts +15 -0
  19. package/dist/v2/core/resources/payments.d.ts +50 -3
  20. package/dist/v2/core/resources/payments.js +38 -7
  21. package/dist/v2/core/utils/pluginConfig.js +40 -5
  22. package/dist/v2/core/utils/previewMode.d.ts +3 -0
  23. package/dist/v2/core/utils/previewMode.js +44 -14
  24. package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
  25. package/dist/v2/core/utils/previewModeIndicator.js +414 -0
  26. package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
  27. package/dist/v2/core/utils/tokenStorage.js +15 -1
  28. package/dist/v2/index.d.ts +6 -1
  29. package/dist/v2/index.js +6 -1
  30. package/dist/v2/react/components/ApplePayButton.d.ts +21 -121
  31. package/dist/v2/react/components/ApplePayButton.js +221 -290
  32. package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
  33. package/dist/v2/react/components/FunnelScriptInjector.js +128 -24
  34. package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
  35. package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
  36. package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
  37. package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
  38. package/dist/v2/react/hooks/useFunnel.d.ts +42 -6
  39. package/dist/v2/react/hooks/useFunnel.js +25 -5
  40. package/dist/v2/react/hooks/usePaymentPolling.d.ts +9 -3
  41. package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
  42. package/dist/v2/react/hooks/usePaymentQuery.d.ts +32 -2
  43. package/dist/v2/react/hooks/usePaymentQuery.js +304 -7
  44. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
  45. package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
  46. package/dist/v2/react/hooks/useStepConfig.d.ts +62 -0
  47. package/dist/v2/react/hooks/useStepConfig.js +52 -0
  48. package/dist/v2/react/index.d.ts +9 -3
  49. package/dist/v2/react/index.js +5 -1
  50. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +27 -19
  51. package/dist/v2/react/providers/TagadaProvider.js +7 -7
  52. package/dist/v2/standalone/external-tracker.d.ts +2 -0
  53. package/dist/v2/standalone/external-tracker.js +6 -3
  54. package/package.json +112 -112
  55. package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
  56. package/dist/v2/react/hooks/useApplePay.js +0 -247
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Payment Retrieve Hook using TanStack Query (V2)
3
+ * Handles payment status retrieval after external redirects
4
+ * This is useful for processors that require server-side status checks (e.g., some 3DS flows)
5
+ */
6
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
7
+ import { PaymentsResource } from '../../core/resources/payments';
8
+ import { usePaymentPolling } from './usePaymentPolling';
9
+ import { getGlobalApiClient } from './useApiQuery';
10
+ export function usePaymentRetrieve() {
11
+ const [isLoading, setIsLoading] = useState(false);
12
+ const [error, setError] = useState(null);
13
+ // Create payments resource client
14
+ const paymentsResource = useMemo(() => {
15
+ try {
16
+ return new PaymentsResource(getGlobalApiClient());
17
+ }
18
+ catch (error) {
19
+ throw new Error('Failed to initialize payments resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
20
+ }
21
+ }, []);
22
+ const { startPolling } = usePaymentPolling();
23
+ // Refs to prevent multiple simultaneous calls
24
+ const isPollingRef = useRef(false);
25
+ const pollIntervalRef = useRef(null);
26
+ const attemptsRef = useRef(0);
27
+ const isPageLoadedRef = useRef(false);
28
+ // Check if page is fully loaded
29
+ const checkPageLoaded = useCallback(() => {
30
+ return new Promise((resolve) => {
31
+ // If already loaded, resolve immediately
32
+ if (document.readyState === 'complete') {
33
+ isPageLoadedRef.current = true;
34
+ resolve();
35
+ return;
36
+ }
37
+ // Wait for load event
38
+ const handleLoad = () => {
39
+ isPageLoadedRef.current = true;
40
+ window.removeEventListener('load', handleLoad);
41
+ resolve();
42
+ };
43
+ window.addEventListener('load', handleLoad);
44
+ // Fallback: resolve after a short delay if load event doesn't fire
45
+ setTimeout(() => {
46
+ if (!isPageLoadedRef.current) {
47
+ isPageLoadedRef.current = true;
48
+ window.removeEventListener('load', handleLoad);
49
+ resolve();
50
+ }
51
+ }, 1000);
52
+ });
53
+ }, []);
54
+ // Cleanup function
55
+ const cleanup = useCallback(() => {
56
+ if (pollIntervalRef.current) {
57
+ clearInterval(pollIntervalRef.current);
58
+ pollIntervalRef.current = null;
59
+ }
60
+ isPollingRef.current = false;
61
+ attemptsRef.current = 0;
62
+ setIsLoading(false);
63
+ // Clean up query parameters
64
+ if (typeof window !== 'undefined') {
65
+ const newUrl = window.location.pathname;
66
+ window.history.replaceState({}, document.title, newUrl);
67
+ }
68
+ }, []);
69
+ // Start retrieve polling
70
+ const startRetrievePolling = useCallback(async (paymentId) => {
71
+ // Prevent multiple simultaneous calls
72
+ if (isPollingRef.current) {
73
+ return;
74
+ }
75
+ // Wait for page to be fully loaded
76
+ await checkPageLoaded();
77
+ // Double-check after page load
78
+ if (isPollingRef.current) {
79
+ return;
80
+ }
81
+ isPollingRef.current = true;
82
+ setIsLoading(true);
83
+ attemptsRef.current = 0;
84
+ const maxAttempts = 10;
85
+ const pollInterval = 3000; // 3 seconds
86
+ const checkRetrieve = async () => {
87
+ try {
88
+ attemptsRef.current++;
89
+ const result = await paymentsResource.retrievePayment(paymentId);
90
+ const status = result?.retrieveResult?.status || result?.status;
91
+ // Fetch payment to check for requireAction
92
+ const payment = await paymentsResource.getPaymentStatus(paymentId);
93
+ // Check if payment requires action
94
+ if (payment?.requireAction !== 'none' && payment?.requireActionData && !payment?.requireActionData?.processed) {
95
+ cleanup();
96
+ // Payment requires new action - would need to be handled by parent component
97
+ return;
98
+ }
99
+ // Check if payment is succeeded
100
+ if (result?.retrieveResult?.success && status === 'succeeded') {
101
+ cleanup();
102
+ // Start regular polling to handle UI updates
103
+ startPolling(paymentId, {
104
+ onSuccess: () => {
105
+ // Payment succeeded after retrieve
106
+ },
107
+ onFailure: (errorMsg) => {
108
+ setError(errorMsg);
109
+ },
110
+ });
111
+ return;
112
+ }
113
+ // Check if payment is declined or error
114
+ if (status === 'declined' || status === 'error') {
115
+ cleanup();
116
+ const errorMsg = result?.retrieveResult?.message || result?.message || 'Payment failed';
117
+ setError(errorMsg);
118
+ return;
119
+ }
120
+ // If still pending and haven't reached max attempts, continue polling
121
+ if (attemptsRef.current < maxAttempts && status === 'pending') {
122
+ return;
123
+ }
124
+ // Max attempts reached or unknown status
125
+ if (attemptsRef.current >= maxAttempts) {
126
+ cleanup();
127
+ const errorMsg = 'Payment status check timed out. Please check your payment status manually.';
128
+ setError(errorMsg);
129
+ }
130
+ }
131
+ catch (error) {
132
+ // On error, continue polling unless we've reached max attempts
133
+ if (attemptsRef.current >= maxAttempts) {
134
+ cleanup();
135
+ const errorMsg = error instanceof Error ? error.message : 'Failed to retrieve payment status';
136
+ setError(errorMsg);
137
+ }
138
+ }
139
+ };
140
+ // Initial check
141
+ await checkRetrieve();
142
+ // Set up polling interval
143
+ pollIntervalRef.current = setInterval(async () => {
144
+ if (attemptsRef.current >= maxAttempts || !isPollingRef.current) {
145
+ cleanup();
146
+ return;
147
+ }
148
+ await checkRetrieve();
149
+ }, pollInterval);
150
+ }, [paymentsResource, startPolling, cleanup, checkPageLoaded]);
151
+ // Cleanup on unmount
152
+ useEffect(() => {
153
+ return () => {
154
+ cleanup();
155
+ };
156
+ }, [cleanup]);
157
+ // Handle retrieve mode from URL query parameters
158
+ useEffect(() => {
159
+ if (typeof window === 'undefined')
160
+ return;
161
+ // Check for payment retrieve mode in URL
162
+ const urlParams = new URLSearchParams(window.location.search);
163
+ const paymentMode = urlParams.get('mode');
164
+ const paymentIdFromUrl = urlParams.get('paymentId');
165
+ if (paymentMode === 'retrieve' && paymentIdFromUrl) {
166
+ void startRetrievePolling(paymentIdFromUrl);
167
+ }
168
+ }, [startRetrievePolling]);
169
+ return {
170
+ startRetrievePolling,
171
+ isLoading,
172
+ error,
173
+ isPolling: isPollingRef.current,
174
+ };
175
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * useStepConfig Hook - Access runtime step configuration from HTML injection
3
+ *
4
+ * This hook provides access to step-specific configuration that is injected
5
+ * into the HTML by the CRM when the page is served. This includes:
6
+ * - Payment flow overrides
7
+ * - Static resources (offer, product IDs for A/B variants)
8
+ * - Custom scripts
9
+ * - Pixel tracking configuration
10
+ *
11
+ * Usage:
12
+ * ```tsx
13
+ * const { stepConfig, paymentFlowId, staticResources } = useStepConfig();
14
+ *
15
+ * // Access payment flow override
16
+ * if (paymentFlowId) {
17
+ * console.log('Using custom payment flow:', paymentFlowId);
18
+ * }
19
+ *
20
+ * // Access static resources (e.g., offer ID for current A/B variant)
21
+ * const offerId = staticResources?.offer;
22
+ * ```
23
+ */
24
+ import { RuntimeStepConfig } from '../../core/funnelClient';
25
+ export interface UseStepConfigResult {
26
+ /**
27
+ * Full step configuration object
28
+ * Contains payment, staticResources, scripts, and pixels
29
+ */
30
+ stepConfig: RuntimeStepConfig | undefined;
31
+ /**
32
+ * Payment flow ID override for this step
33
+ * If set, this payment flow should be used instead of the store default
34
+ */
35
+ paymentFlowId: string | undefined;
36
+ /**
37
+ * Static resources assigned to this step/variant
38
+ * For A/B tests, this contains the resources for the specific variant the user landed on
39
+ * e.g., { offer: 'offer_xxx', product: 'product_xxx' }
40
+ */
41
+ staticResources: Record<string, string> | undefined;
42
+ /**
43
+ * Get scripts for a specific injection position
44
+ * Only returns enabled scripts
45
+ * @param position - Where the scripts should be injected
46
+ */
47
+ getScripts: (position?: 'head-start' | 'head-end' | 'body-start' | 'body-end') => RuntimeStepConfig['scripts'];
48
+ /**
49
+ * Pixel tracking configuration
50
+ * e.g., { facebook: 'pixel_id', google: 'ga_id' }
51
+ */
52
+ pixels: Record<string, string> | undefined;
53
+ }
54
+ /**
55
+ * Hook to access runtime step configuration injected via HTML
56
+ *
57
+ * The step config is read from window.__TGD_STEP_CONFIG__ or the x-step-config meta tag.
58
+ * Values are memoized and only re-computed on mount.
59
+ *
60
+ * @returns Step configuration values and helpers
61
+ */
62
+ export declare function useStepConfig(): UseStepConfigResult;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * useStepConfig Hook - Access runtime step configuration from HTML injection
3
+ *
4
+ * This hook provides access to step-specific configuration that is injected
5
+ * into the HTML by the CRM when the page is served. This includes:
6
+ * - Payment flow overrides
7
+ * - Static resources (offer, product IDs for A/B variants)
8
+ * - Custom scripts
9
+ * - Pixel tracking configuration
10
+ *
11
+ * Usage:
12
+ * ```tsx
13
+ * const { stepConfig, paymentFlowId, staticResources } = useStepConfig();
14
+ *
15
+ * // Access payment flow override
16
+ * if (paymentFlowId) {
17
+ * console.log('Using custom payment flow:', paymentFlowId);
18
+ * }
19
+ *
20
+ * // Access static resources (e.g., offer ID for current A/B variant)
21
+ * const offerId = staticResources?.offer;
22
+ * ```
23
+ */
24
+ import { useMemo } from 'react';
25
+ import { getAssignedStepConfig, getAssignedPaymentFlowId, getAssignedStaticResources, getAssignedScripts, } from '../../core/funnelClient';
26
+ /**
27
+ * Hook to access runtime step configuration injected via HTML
28
+ *
29
+ * The step config is read from window.__TGD_STEP_CONFIG__ or the x-step-config meta tag.
30
+ * Values are memoized and only re-computed on mount.
31
+ *
32
+ * @returns Step configuration values and helpers
33
+ */
34
+ export function useStepConfig() {
35
+ // Read once on mount - these values don't change during the page lifecycle
36
+ const stepConfig = useMemo(() => getAssignedStepConfig(), []);
37
+ const paymentFlowId = useMemo(() => getAssignedPaymentFlowId(), []);
38
+ const staticResources = useMemo(() => getAssignedStaticResources(), []);
39
+ // Create a stable reference for getScripts
40
+ const getScripts = useMemo(() => {
41
+ return (position) => {
42
+ return getAssignedScripts(position);
43
+ };
44
+ }, []);
45
+ return {
46
+ stepConfig,
47
+ paymentFlowId,
48
+ staticResources,
49
+ getScripts,
50
+ pixels: stepConfig?.pixels,
51
+ };
52
+ }
@@ -7,7 +7,8 @@ export { TagadaProvider, useTagadaContext } from './providers/TagadaProvider';
7
7
  export type { DebugScript } from './providers/TagadaProvider';
8
8
  export { ApplePayButton } from './components/ApplePayButton';
9
9
  export { GooglePayButton } from './components/GooglePayButton';
10
- export { useApplePay } from './hooks/useApplePay';
10
+ export { PreviewModeIndicator } from './components/PreviewModeIndicator';
11
+ export { useApplePayCheckout } from './hooks/useApplePayCheckout';
11
12
  export { useAuth } from './hooks/useAuth';
12
13
  export { useCheckoutToken } from './hooks/useCheckoutToken';
13
14
  export { useClubOffers } from './hooks/useClubOffers';
@@ -32,6 +33,7 @@ export { usePreviewOffer } from './hooks/usePreviewOffer';
32
33
  export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
33
34
  export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
34
35
  export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';
36
+ export { usePaymentRetrieve } from './hooks/usePaymentRetrieve';
35
37
  export { usePostPurchasesQuery as usePostPurchases } from './hooks/usePostPurchasesQuery';
36
38
  export { useProductsQuery as useProducts } from './hooks/useProductsQuery';
37
39
  export { usePromotionsQuery as usePromotions } from './hooks/usePromotionsQuery';
@@ -42,8 +44,9 @@ export { useThreedsModal } from './hooks/useThreedsModal';
42
44
  export { useTranslation } from './hooks/useTranslation';
43
45
  export { useVipOffersQuery as useVipOffers } from './hooks/useVipOffersQuery';
44
46
  export { useFunnel } from './hooks/useFunnel';
47
+ export { useStepConfig } from './hooks/useStepConfig';
45
48
  export { useFunnel as useFunnelLegacy, useSimpleFunnel } from './hooks/useFunnelLegacy';
46
- export type { UseApplePayOptions, UseApplePayResult } from './hooks/useApplePay';
49
+ export type { UseApplePayCheckoutOptions } from './hooks/useApplePayCheckout';
47
50
  export type { UseCheckoutTokenOptions, UseCheckoutTokenResult } from './hooks/useCheckoutToken';
48
51
  export type { ClubOffer, ClubOfferItem, ClubOfferLineItem, ClubOfferSummary, UseClubOffersOptions, UseClubOffersResult } from './hooks/useClubOffers';
49
52
  export type { UseCreditsOptions, UseCreditsResult } from './hooks/useCredits';
@@ -55,6 +58,7 @@ export type { ExpressPaymentMethodsContextType, ExpressPaymentMethodsProviderPro
55
58
  export type { UseLoginOptions, UseLoginResult } from './hooks/useLogin';
56
59
  export type { ApplePayButtonProps } from './components/ApplePayButton';
57
60
  export type { GooglePayButtonProps } from './components/GooglePayButton';
61
+ export type { PreviewModeIndicatorProps } from './components/PreviewModeIndicator';
58
62
  export type { GeoLocationData, UseGeoLocationOptions, UseGeoLocationReturn } from './hooks/useGeoLocation';
59
63
  export type { ExtractedAddress, GooglePlaceDetails, GooglePrediction, UseGoogleAutocompleteOptions, UseGoogleAutocompleteResult } from './hooks/useGoogleAutocomplete';
60
64
  export type { ISOCountry, ISORegion, UseISODataResult } from './hooks/useISOData';
@@ -64,13 +68,15 @@ export { FunnelActionType } from '../core/resources/funnel';
64
68
  export type { UseCheckoutQueryOptions as UseCheckoutOptions, UseCheckoutQueryResult as UseCheckoutResult } from './hooks/useCheckoutQuery';
65
69
  export type { UseDiscountsQueryOptions as UseDiscountsOptions, UseDiscountsQueryResult as UseDiscountsResult } from './hooks/useDiscountsQuery';
66
70
  export type { FunnelAction, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from '../core/resources/funnel';
67
- export type { FunnelContextValue } from './hooks/useFunnel';
71
+ export type { FunnelContextValue, StepConfigValue } from './hooks/useFunnel';
72
+ export type { UseStepConfigResult } from './hooks/useStepConfig';
68
73
  export type { UseFunnelOptions as UseFunnelLegacyOptions, UseFunnelResult as UseFunnelLegacyResult } from './hooks/useFunnelLegacy';
69
74
  export type { AvailableVariant, LineItemSelection, OfferLineItem, OfferPreviewSummary, UseOfferQueryOptions as UseOfferOptions, UseOfferQueryResult as UseOfferResult } from './hooks/useOfferQuery';
70
75
  export type { PreviewOfferSummary, UsePreviewOfferOptions, UsePreviewOfferResult } from './hooks/usePreviewOffer';
71
76
  export type { UseOrderBumpQueryOptions as UseOrderBumpOptions, UseOrderBumpQueryResult as UseOrderBumpResult } from './hooks/useOrderBumpQuery';
72
77
  export type { UseOrderQueryOptions as UseOrderOptions, UseOrderQueryResult as UseOrderResult } from './hooks/useOrderQuery';
73
78
  export type { PaymentHook as UsePaymentResult } from './hooks/usePaymentQuery';
79
+ export type { PaymentRetrieveHook as UsePaymentRetrieveResult } from './hooks/usePaymentRetrieve';
74
80
  export type { UsePostPurchasesQueryOptions as UsePostPurchasesOptions, UsePostPurchasesQueryResult as UsePostPurchasesResult } from './hooks/usePostPurchasesQuery';
75
81
  export type { UseProductsQueryOptions as UseProductsOptions, UseProductsQueryResult as UseProductsResult } from './hooks/useProductsQuery';
76
82
  export type { UsePromotionsQueryOptions as UsePromotionsOptions, UsePromotionsQueryResult as UsePromotionsResult } from './hooks/usePromotionsQuery';
@@ -8,8 +8,9 @@ export { TagadaProvider, useTagadaContext } from './providers/TagadaProvider';
8
8
  // Components
9
9
  export { ApplePayButton } from './components/ApplePayButton';
10
10
  export { GooglePayButton } from './components/GooglePayButton';
11
+ export { PreviewModeIndicator } from './components/PreviewModeIndicator';
11
12
  // Hooks
12
- export { useApplePay } from './hooks/useApplePay';
13
+ export { useApplePayCheckout } from './hooks/useApplePayCheckout';
13
14
  export { useAuth } from './hooks/useAuth';
14
15
  export { useCheckoutToken } from './hooks/useCheckoutToken';
15
16
  export { useClubOffers } from './hooks/useClubOffers';
@@ -35,6 +36,7 @@ export { usePreviewOffer } from './hooks/usePreviewOffer';
35
36
  export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
36
37
  export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
37
38
  export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';
39
+ export { usePaymentRetrieve } from './hooks/usePaymentRetrieve';
38
40
  export { usePostPurchasesQuery as usePostPurchases } from './hooks/usePostPurchasesQuery';
39
41
  export { useProductsQuery as useProducts } from './hooks/useProductsQuery';
40
42
  export { usePromotionsQuery as usePromotions } from './hooks/usePromotionsQuery';
@@ -46,6 +48,8 @@ export { useTranslation } from './hooks/useTranslation';
46
48
  export { useVipOffersQuery as useVipOffers } from './hooks/useVipOffersQuery';
47
49
  // Funnel hooks
48
50
  export { useFunnel } from './hooks/useFunnel';
51
+ // Step config hook (access runtime configuration from HTML injection)
52
+ export { useStepConfig } from './hooks/useStepConfig';
49
53
  // Legacy funnel hooks (deprecated - use TagadaProvider + useFunnel instead)
50
54
  export { useFunnel as useFunnelLegacy, useSimpleFunnel } from './hooks/useFunnelLegacy';
51
55
  // TanStack Query types
@@ -40,8 +40,11 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
40
40
  setAvailableExpressPaymentMethodIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
41
41
  }, []);
42
42
  const minorUnitsToCurrencyString = useCallback((amountMinor, currency) => {
43
- if (!amountMinor || !currency)
44
- return '0.00';
43
+ // Fail safely - don't allow invalid data to become '0.00'
44
+ if (amountMinor === undefined || amountMinor === null || !currency) {
45
+ throw new Error(`Invalid currency data: amountMinor=${amountMinor}, currency=${currency}`);
46
+ }
47
+ // 0 is a valid amount (e.g., free shipping)
45
48
  return (amountMinor / 100).toFixed(2);
46
49
  }, []);
47
50
  // Convert shipping rates to express shipping methods
@@ -69,9 +72,11 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
69
72
  // Update checkout session address mutation
70
73
  const updateAddressMutation = useMutation({
71
74
  mutationFn: (data) => expressPaymentResource.updateCheckoutSessionAddress(checkoutSessionId, { data }),
72
- onSuccess: () => {
73
- // Invalidate checkout query to refresh data
74
- void queryClient.invalidateQueries({ queryKey: ['checkout'] });
75
+ onSuccess: async () => {
76
+ // Invalidate and wait for checkout query to refetch
77
+ await queryClient.invalidateQueries({ queryKey: ['checkout'] });
78
+ // Small delay to ensure parent's query has time to refetch
79
+ await new Promise((resolve) => setTimeout(resolve, 300));
75
80
  },
76
81
  });
77
82
  // Update customer email mutation
@@ -79,35 +84,38 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
79
84
  mutationFn: (data) => expressPaymentResource.updateCustomerEmail(customerId, { data }),
80
85
  });
81
86
  // Recompute order summary (refetch after updates)
87
+ // Uses current checkout prop and refetches shipping rates
82
88
  const reComputeOrderSummary = useCallback(async () => {
83
89
  try {
84
- // Invalidate and refetch checkout to get latest data
85
- await queryClient.invalidateQueries({ queryKey: ['checkout'] });
90
+ // Only refetch shipping rates - checkout is managed by parent
86
91
  await refetchRates();
87
- // Wait a bit for queries to settle
88
- await new Promise((resolve) => setTimeout(resolve, 100));
89
- // Get fresh data from cache
90
- const freshCheckout = queryClient.getQueryData(['checkout']);
91
- const freshSummary = freshCheckout?.summary;
92
- if (!freshSummary || !shippingRates)
93
- return;
92
+ // Use current checkout prop (will be updated by parent after address mutation)
93
+ const currentSummary = checkout?.summary;
94
+ if (!currentSummary || !shippingRates) {
95
+ console.error('[ExpressProvider] Missing data:', {
96
+ hasSummary: !!currentSummary,
97
+ hasShippingRates: !!shippingRates,
98
+ checkout,
99
+ });
100
+ return undefined;
101
+ }
94
102
  const recomputedLineItems = [
95
103
  {
96
104
  label: 'Subtotal',
97
- amount: minorUnitsToCurrencyString(freshSummary.subtotalAdjustedAmount, freshSummary.currency),
105
+ amount: minorUnitsToCurrencyString(currentSummary.subtotalAdjustedAmount, currentSummary.currency),
98
106
  },
99
107
  {
100
108
  label: 'Shipping',
101
- amount: minorUnitsToCurrencyString(freshSummary.shippingCost ?? 0, freshSummary.currency),
109
+ amount: minorUnitsToCurrencyString(currentSummary.shippingCost ?? 0, currentSummary.currency),
102
110
  },
103
111
  {
104
112
  label: 'Tax',
105
- amount: minorUnitsToCurrencyString(freshSummary.totalTaxAmount, freshSummary.currency),
113
+ amount: minorUnitsToCurrencyString(currentSummary.totalTaxAmount, currentSummary.currency),
106
114
  },
107
115
  ];
108
116
  const total = {
109
117
  label: 'Order Total',
110
- amount: minorUnitsToCurrencyString(freshSummary.totalAdjustedAmount, freshSummary.currency),
118
+ amount: minorUnitsToCurrencyString(currentSummary.totalAdjustedAmount, currentSummary.currency),
111
119
  };
112
120
  const recomputedShippingMethods = (shippingRates || []).map((rate) => ({
113
121
  label: rate.shippingRateName,
@@ -125,7 +133,7 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
125
133
  console.error('Error recomputing order summary:', e);
126
134
  return undefined;
127
135
  }
128
- }, [queryClient, refetchRates, shippingRates, minorUnitsToCurrencyString]);
136
+ }, [refetchRates, shippingRates, minorUnitsToCurrencyString, checkout]);
129
137
  // Update checkout session values
130
138
  const updateCheckoutSessionValues = useCallback(async (input) => {
131
139
  await updateAddressMutation.mutateAsync(input.data);
@@ -37,11 +37,11 @@ const InitializationLoader = () => (_jsxs("div", { style: {
37
37
  borderTop: '1.5px solid #9ca3af',
38
38
  borderRadius: '50%',
39
39
  animation: 'tagada-spin 1s linear infinite',
40
- } }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
41
- @keyframes tagada-spin {
42
- 0% { transform: rotate(0deg); }
43
- 100% { transform: rotate(360deg); }
44
- }
40
+ } }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
41
+ @keyframes tagada-spin {
42
+ 0% { transform: rotate(0deg); }
43
+ 100% { transform: rotate(360deg); }
44
+ }
45
45
  ` })] }));
46
46
  const TagadaContext = createContext(null);
47
47
  export function TagadaProvider({ children, environment, customApiConfig, debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, features, funnelId, autoInitializeFunnel = true, onNavigate, onFunnelError, debugScripts = [], }) {
@@ -248,8 +248,8 @@ export function TagadaProvider({ children, environment, customApiConfig, debugMo
248
248
  window.location.href = result.url;
249
249
  }
250
250
  };
251
- const next = async (event) => {
252
- const result = await client.funnel.navigate(event);
251
+ const next = async (event, options) => {
252
+ const result = await client.funnel.navigate(event, options);
253
253
  handleNavigationResult(result);
254
254
  return result;
255
255
  };
@@ -71,6 +71,8 @@ export interface NavigateOptions {
71
71
  eventData?: Record<string, unknown>;
72
72
  /** Override return URL */
73
73
  returnUrl?: string;
74
+ /** Disable auto-redirect to control navigation manually (default: true) */
75
+ autoRedirect?: boolean;
74
76
  }
75
77
  declare class TagadaExternalTracker {
76
78
  private config;
@@ -135,15 +135,18 @@ class TagadaExternalTracker {
135
135
  throw new Error('Tracker not initialized. Call init() first.');
136
136
  }
137
137
  log(this.config.debug, '🚀 Navigating:', options);
138
+ // Determine if we should auto-redirect (default: true)
139
+ const shouldAutoRedirect = options.autoRedirect !== false;
138
140
  try {
139
141
  const result = await this.client.funnel.navigate({
140
142
  type: options.eventType,
141
143
  data: options.eventData || {},
142
- });
144
+ }, { autoRedirect: false } // Always disable SDK auto-redirect, we handle it here
145
+ );
143
146
  if (result?.url) {
144
147
  log(this.config.debug, '✅ Navigation result:', result.url);
145
- // Redirect to the next step
146
- if (typeof window !== 'undefined') {
148
+ // Only redirect if autoRedirect is enabled
149
+ if (shouldAutoRedirect && typeof window !== 'undefined') {
147
150
  window.location.href = options.returnUrl || result.url;
148
151
  }
149
152
  return { url: result.url };