@tagadapay/plugin-sdk 3.1.22 → 3.1.25
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/build-cdn.js +274 -6
- package/dist/external-tracker.js +476 -6774
- package/dist/external-tracker.min.js +2 -25
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +14 -4
- package/dist/react/config/payment.js +47 -9
- package/dist/react/hooks/useCheckout.d.ts +3 -0
- package/dist/react/hooks/useCheckout.js +11 -3
- package/dist/react/hooks/usePluginConfig.js +9 -10
- package/dist/react/providers/TagadaProvider.js +1 -1
- package/dist/tagada-react-sdk-minimal.min.js +36 -0
- package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
- package/dist/tagada-react-sdk.js +37988 -0
- package/dist/tagada-react-sdk.min.js +78 -0
- package/dist/tagada-react-sdk.min.js.map +7 -0
- package/dist/tagada-sdk.js +7847 -6420
- package/dist/tagada-sdk.min.js +4 -22
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/cdn-react-minimal.d.ts +23 -0
- package/dist/v2/cdn-react-minimal.js +26 -0
- package/dist/v2/core/client.js +2 -1
- package/dist/v2/core/config/environment.js +2 -1
- package/dist/v2/core/funnelClient.d.ts +106 -10
- package/dist/v2/core/funnelClient.js +122 -28
- package/dist/v2/core/index.d.ts +0 -1
- package/dist/v2/core/index.js +0 -2
- package/dist/v2/core/isoData.d.ts +4 -4
- package/dist/v2/core/isoData.js +7 -7
- package/dist/v2/core/pixelMapping.js +64 -26
- package/dist/v2/core/resources/apiClient.d.ts +18 -14
- package/dist/v2/core/resources/apiClient.js +151 -109
- package/dist/v2/core/resources/checkout.d.ts +10 -0
- package/dist/v2/core/resources/checkout.js +6 -0
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
- package/dist/v2/core/resources/index.d.ts +1 -1
- package/dist/v2/core/resources/index.js +1 -1
- package/dist/v2/core/resources/offers.js +4 -4
- package/dist/v2/core/resources/payments.d.ts +8 -2
- package/dist/v2/core/resources/payments.js +1 -0
- package/dist/v2/core/resources/postPurchases.d.ts +17 -0
- package/dist/v2/core/resources/postPurchases.js +20 -0
- package/dist/v2/core/utils/currency.d.ts +3 -0
- package/dist/v2/core/utils/currency.js +40 -2
- package/dist/v2/core/utils/deviceInfo.d.ts +1 -10
- package/dist/v2/core/utils/deviceInfo.js +153 -76
- package/dist/v2/core/utils/order.d.ts +2 -0
- package/dist/v2/core/utils/pluginConfig.js +18 -22
- package/dist/v2/core/utils/previewMode.js +12 -0
- package/dist/v2/index.d.ts +4 -3
- package/dist/v2/index.js +4 -2
- package/dist/v2/react/components/ApplePayButton.js +39 -16
- package/dist/v2/react/components/FunnelScriptInjector.js +145 -77
- package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
- package/dist/v2/react/components/StripeExpressButton.js +170 -0
- package/dist/v2/react/components/WhopCheckout.js +7 -1
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +1 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +21 -3
- package/dist/v2/react/hooks/useApiQuery.d.ts +1 -1
- package/dist/v2/react/hooks/useApiQuery.js +1 -1
- package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +10 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +27 -15
- package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
- package/dist/v2/react/hooks/useFunnel.js +8 -4
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
- package/dist/v2/react/hooks/useISOData.d.ts +2 -5
- package/dist/v2/react/hooks/useISOData.js +25 -26
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +2 -2
- package/dist/v2/react/hooks/usePixelTracking.js +151 -70
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
- package/dist/v2/react/hooks/usePreviewOffer.js +1 -1
- package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
- package/dist/v2/react/hooks/useRemappableParams.js +23 -23
- package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
- package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
- package/dist/v2/react/hooks/useStepConfig.js +14 -7
- package/dist/v2/react/hooks/useTranslation.js +23 -8
- package/dist/v2/react/index.d.ts +8 -1
- package/dist/v2/react/index.js +3 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +8 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +106 -10
- package/dist/v2/react/providers/TagadaProvider.js +5 -5
- package/dist/v2/standalone/index.d.ts +21 -3
- package/dist/v2/standalone/index.js +25 -3
- package/dist/v2/standalone/payment-service.d.ts +134 -0
- package/dist/v2/standalone/payment-service.js +929 -0
- package/package.json +4 -2
|
@@ -5,12 +5,19 @@
|
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { CheckoutResource } from '../../core/resources/checkout';
|
|
8
|
+
import { getAssignedOrderBumpOfferIds } from '../../core/funnelClient';
|
|
8
9
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
10
|
import { getGlobalApiClient } from './useApiQuery';
|
|
10
11
|
import { useCurrency } from './useCurrency';
|
|
11
12
|
import { usePluginConfig } from './usePluginConfig';
|
|
13
|
+
const DEFAULT_MESSAGES = {
|
|
14
|
+
sessionTimeout: 'Session initialization timeout. Please refresh the page and try again.',
|
|
15
|
+
initFailed: 'Failed to initialize checkout resource',
|
|
16
|
+
noCheckoutSession: 'No checkout session available',
|
|
17
|
+
};
|
|
12
18
|
export function useCheckoutQuery(options = {}) {
|
|
13
|
-
const { checkoutToken: providedToken, enabled = true } = options;
|
|
19
|
+
const { checkoutToken: providedToken, enabled = true, messages: userMessages } = options;
|
|
20
|
+
const messages = { ...DEFAULT_MESSAGES, ...userMessages };
|
|
14
21
|
const { storeId } = usePluginConfig();
|
|
15
22
|
const currency = useCurrency();
|
|
16
23
|
const queryClient = useQueryClient();
|
|
@@ -48,7 +55,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
48
55
|
if (sessionResolvers.current.size === 0) {
|
|
49
56
|
pendingSessionPromise.current = null;
|
|
50
57
|
}
|
|
51
|
-
reject(new Error(
|
|
58
|
+
reject(new Error(messages.sessionTimeout));
|
|
52
59
|
}
|
|
53
60
|
}, 10000);
|
|
54
61
|
});
|
|
@@ -60,7 +67,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
60
67
|
return new CheckoutResource(getGlobalApiClient());
|
|
61
68
|
}
|
|
62
69
|
catch (error) {
|
|
63
|
-
throw new Error(
|
|
70
|
+
throw new Error(messages.initFailed + ': ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
64
71
|
}
|
|
65
72
|
}, []);
|
|
66
73
|
// Internal token state that can be updated after init
|
|
@@ -73,10 +80,14 @@ export function useCheckoutQuery(options = {}) {
|
|
|
73
80
|
}, [providedToken, internalToken]);
|
|
74
81
|
// Use provided token or internal token
|
|
75
82
|
const checkoutToken = providedToken || internalToken;
|
|
83
|
+
// Only send currency to the backend when explicitly set via URL param or persisted storage.
|
|
84
|
+
// When not explicit, the backend uses the session's own selectedPresentmentCurrency --
|
|
85
|
+
// the frontend then reads the currency from the response, never guesses.
|
|
86
|
+
const explicitCurrency = currency.isExplicit ? currency.code : undefined;
|
|
76
87
|
// Main checkout query
|
|
77
88
|
const { data: checkout, isLoading, error, isSuccess, refetch, } = useQuery({
|
|
78
|
-
queryKey: ['checkout', checkoutToken,
|
|
79
|
-
queryFn: () => checkoutResource.getCheckout(checkoutToken,
|
|
89
|
+
queryKey: ['checkout', checkoutToken, explicitCurrency],
|
|
90
|
+
queryFn: () => checkoutResource.getCheckout(checkoutToken, explicitCurrency),
|
|
80
91
|
enabled: enabled && !!checkoutToken && isSessionInitialized,
|
|
81
92
|
staleTime: 30000, // 30 seconds
|
|
82
93
|
refetchOnWindowFocus: false,
|
|
@@ -90,16 +101,17 @@ export function useCheckoutQuery(options = {}) {
|
|
|
90
101
|
// Initialize checkout mutation (async mode for fast response)
|
|
91
102
|
const initMutation = useMutation({
|
|
92
103
|
mutationFn: async (params) => {
|
|
104
|
+
const enabledOrderBumpOfferIds = params.enabledOrderBumpOfferIds ?? getAssignedOrderBumpOfferIds();
|
|
93
105
|
const requestBody = {
|
|
94
106
|
...params,
|
|
95
107
|
storeId: params.storeId || storeId,
|
|
96
108
|
returnUrl: params.returnUrl || window.location.origin,
|
|
97
|
-
// Include customerId from session to prevent duplicate customer creation
|
|
98
109
|
customerId: params.customerId || session?.customerId,
|
|
99
110
|
customer: {
|
|
100
111
|
...params.customer,
|
|
101
112
|
currency: params.customer?.currency ?? currency.code,
|
|
102
113
|
},
|
|
114
|
+
...(enabledOrderBumpOfferIds && { enabledOrderBumpOfferIds }),
|
|
103
115
|
};
|
|
104
116
|
// Use async mode for fast response (~50ms vs 2-5s)
|
|
105
117
|
const asyncResponse = await checkoutResource.initCheckoutAsync(requestBody);
|
|
@@ -125,7 +137,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
125
137
|
const replaceSessionLineItemsMutation = useMutation({
|
|
126
138
|
mutationFn: ({ lineItems }) => {
|
|
127
139
|
if (!checkout?.checkoutSession?.id) {
|
|
128
|
-
throw new Error(
|
|
140
|
+
throw new Error(messages.noCheckoutSession);
|
|
129
141
|
}
|
|
130
142
|
return checkoutResource.replaceSessionLineItems(checkout.checkoutSession.id, lineItems);
|
|
131
143
|
},
|
|
@@ -139,7 +151,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
139
151
|
const lineItemsMutation = useMutation({
|
|
140
152
|
mutationFn: ({ lineItems }) => {
|
|
141
153
|
if (!checkout?.checkoutSession?.id) {
|
|
142
|
-
throw new Error(
|
|
154
|
+
throw new Error(messages.noCheckoutSession);
|
|
143
155
|
}
|
|
144
156
|
return checkoutResource.updateLineItems(checkout.checkoutSession.id, lineItems);
|
|
145
157
|
},
|
|
@@ -184,7 +196,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
184
196
|
const addLineItemsMutation = useMutation({
|
|
185
197
|
mutationFn: ({ lineItems }) => {
|
|
186
198
|
if (!checkout?.checkoutSession?.id) {
|
|
187
|
-
throw new Error(
|
|
199
|
+
throw new Error(messages.noCheckoutSession);
|
|
188
200
|
}
|
|
189
201
|
return checkoutResource.addLineItems(checkout.checkoutSession.id, lineItems);
|
|
190
202
|
},
|
|
@@ -198,7 +210,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
198
210
|
const removeLineItemsMutation = useMutation({
|
|
199
211
|
mutationFn: ({ lineItems }) => {
|
|
200
212
|
if (!checkout?.checkoutSession?.id) {
|
|
201
|
-
throw new Error(
|
|
213
|
+
throw new Error(messages.noCheckoutSession);
|
|
202
214
|
}
|
|
203
215
|
return checkoutResource.removeLineItems(checkout.checkoutSession.id, lineItems);
|
|
204
216
|
},
|
|
@@ -212,7 +224,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
212
224
|
const quantityMutation = useMutation({
|
|
213
225
|
mutationFn: ({ variantId, quantity, priceId }) => {
|
|
214
226
|
if (!checkout?.checkoutSession?.id) {
|
|
215
|
-
throw new Error(
|
|
227
|
+
throw new Error(messages.noCheckoutSession);
|
|
216
228
|
}
|
|
217
229
|
return checkoutResource.setItemQuantity(checkout.checkoutSession.id, variantId, quantity, priceId);
|
|
218
230
|
},
|
|
@@ -226,7 +238,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
226
238
|
const customerMutation = useMutation({
|
|
227
239
|
mutationFn: (data) => {
|
|
228
240
|
if (!checkout?.checkoutSession?.id) {
|
|
229
|
-
throw new Error(
|
|
241
|
+
throw new Error(messages.noCheckoutSession);
|
|
230
242
|
}
|
|
231
243
|
return checkoutResource.updateCustomer(checkout.checkoutSession.id, data);
|
|
232
244
|
},
|
|
@@ -240,7 +252,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
240
252
|
const customerAndSessionMutation = useMutation({
|
|
241
253
|
mutationFn: (data) => {
|
|
242
254
|
if (!checkout?.checkoutSession?.id) {
|
|
243
|
-
throw new Error(
|
|
255
|
+
throw new Error(messages.noCheckoutSession);
|
|
244
256
|
}
|
|
245
257
|
return checkoutResource.updateCustomerAndSessionInfo(checkout.checkoutSession.id, data);
|
|
246
258
|
},
|
|
@@ -254,7 +266,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
254
266
|
const promotionMutation = useMutation({
|
|
255
267
|
mutationFn: ({ code }) => {
|
|
256
268
|
if (!checkout?.checkoutSession?.id) {
|
|
257
|
-
throw new Error(
|
|
269
|
+
throw new Error(messages.noCheckoutSession);
|
|
258
270
|
}
|
|
259
271
|
return checkoutResource.applyPromotionCode(checkout.checkoutSession.id, code);
|
|
260
272
|
},
|
|
@@ -271,7 +283,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
271
283
|
const removePromotionMutation = useMutation({
|
|
272
284
|
mutationFn: ({ promotionId }) => {
|
|
273
285
|
if (!checkout?.checkoutSession?.id) {
|
|
274
|
-
throw new Error(
|
|
286
|
+
throw new Error(messages.noCheckoutSession);
|
|
275
287
|
}
|
|
276
288
|
return checkoutResource.removePromotion(checkout.checkoutSession.id, promotionId);
|
|
277
289
|
},
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* // In any child component:
|
|
15
15
|
* const { context, next, isLoading, stepConfig } = useFunnel();
|
|
16
16
|
*
|
|
17
|
-
* // Access step-specific config (payment flows,
|
|
18
|
-
* const offerId = stepConfig.
|
|
17
|
+
* // Access step-specific config (payment flows, resources, etc.)
|
|
18
|
+
* const offerId = stepConfig.resources?.offer;
|
|
19
19
|
* const paymentFlowId = stepConfig.paymentFlowId;
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
@@ -35,10 +35,11 @@ export interface StepConfigValue {
|
|
|
35
35
|
*/
|
|
36
36
|
paymentFlowId: string | undefined;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* For A/B tests, this contains the resources for the specific variant
|
|
38
|
+
* Resource bindings for this step/variant.
|
|
40
39
|
* e.g., { offer: 'offer_xxx', product: 'product_xxx' }
|
|
41
40
|
*/
|
|
41
|
+
resources: Record<string, string> | undefined;
|
|
42
|
+
/** @deprecated Use `resources` instead */
|
|
42
43
|
staticResources: Record<string, string> | undefined;
|
|
43
44
|
/**
|
|
44
45
|
* Get scripts for a specific injection position
|
|
@@ -54,6 +55,16 @@ export interface StepConfigValue {
|
|
|
54
55
|
[TrackingProvider.SNAPCHAT]?: SnapchatTrackingConfig[];
|
|
55
56
|
[TrackingProvider.GTM]?: GTMTrackingConfig[];
|
|
56
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Enabled order bump offer IDs for this step.
|
|
60
|
+
* undefined = inherit all store bumps, string[] = only these IDs.
|
|
61
|
+
*/
|
|
62
|
+
orderBumpOfferIds: string[] | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Enabled upsell offer IDs for this step.
|
|
65
|
+
* undefined = inherit all store upsells, string[] = only these IDs.
|
|
66
|
+
*/
|
|
67
|
+
upsellOfferIds: string[] | undefined;
|
|
57
68
|
}
|
|
58
69
|
export interface FunnelContextValue extends FunnelState {
|
|
59
70
|
currentStep: {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* // In any child component:
|
|
15
15
|
* const { context, next, isLoading, stepConfig } = useFunnel();
|
|
16
16
|
*
|
|
17
|
-
* // Access step-specific config (payment flows,
|
|
18
|
-
* const offerId = stepConfig.
|
|
17
|
+
* // Access step-specific config (payment flows, resources, etc.)
|
|
18
|
+
* const offerId = stepConfig.resources?.offer;
|
|
19
19
|
* const paymentFlowId = stepConfig.paymentFlowId;
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
import { useMemo } from 'react';
|
|
23
|
-
import { TrackingProvider, getAssignedPaymentFlowId, getAssignedPixels,
|
|
23
|
+
import { TrackingProvider, getAssignedOrderBumpOfferIds, getAssignedPaymentFlowId, getAssignedPixels, getAssignedResources, getAssignedScripts, getAssignedStepConfig, getAssignedUpsellOfferIds, } from '../../core/funnelClient';
|
|
24
24
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
25
25
|
/**
|
|
26
26
|
* Hook to access funnel state and methods
|
|
@@ -35,12 +35,16 @@ export function useFunnel() {
|
|
|
35
35
|
// Compute step config from HTML injection (memoized, computed once on mount)
|
|
36
36
|
const stepConfig = useMemo(() => {
|
|
37
37
|
const raw = getAssignedStepConfig();
|
|
38
|
+
const resources = getAssignedResources();
|
|
38
39
|
return {
|
|
39
40
|
raw,
|
|
40
41
|
paymentFlowId: getAssignedPaymentFlowId(),
|
|
41
|
-
|
|
42
|
+
resources,
|
|
43
|
+
staticResources: resources,
|
|
42
44
|
pixels: getAssignedPixels(),
|
|
43
45
|
getScripts: (position) => getAssignedScripts(position),
|
|
46
|
+
orderBumpOfferIds: getAssignedOrderBumpOfferIds(),
|
|
47
|
+
upsellOfferIds: getAssignedUpsellOfferIds(),
|
|
44
48
|
};
|
|
45
49
|
}, []);
|
|
46
50
|
return {
|
|
@@ -68,6 +68,8 @@ export interface UseGoogleAutocompleteOptions {
|
|
|
68
68
|
version?: string;
|
|
69
69
|
language?: string;
|
|
70
70
|
region?: string;
|
|
71
|
+
/** When true, defer loading the Google Maps script until searchPlaces() is first called. Default: false (eager load). */
|
|
72
|
+
defer?: boolean;
|
|
71
73
|
}
|
|
72
74
|
export interface UseGoogleAutocompleteResult {
|
|
73
75
|
predictions: GooglePrediction[];
|
|
@@ -8,7 +8,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
8
8
|
* Automatically loads the Google Maps JavaScript API with Places library
|
|
9
9
|
*/
|
|
10
10
|
export function useGoogleAutocomplete(options) {
|
|
11
|
-
const { apiKey, libraries = ['places'], version = 'weekly', language, region } = options;
|
|
11
|
+
const { apiKey, libraries = ['places'], version = 'weekly', language, region, defer = false } = options;
|
|
12
12
|
const [predictions, setPredictions] = useState([]);
|
|
13
13
|
const [isLoading, setIsLoading] = useState(false);
|
|
14
14
|
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
|
|
@@ -16,19 +16,16 @@ export function useGoogleAutocomplete(options) {
|
|
|
16
16
|
const placesServiceRef = useRef(null);
|
|
17
17
|
const scriptLoadedRef = useRef(false);
|
|
18
18
|
const debounceTimeoutRef = useRef(null);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
19
|
+
const pendingSearchRef = useRef(null);
|
|
20
|
+
// Shared script injection logic (used by both eager useEffect and lazy trigger)
|
|
21
|
+
const injectScript = useCallback(() => {
|
|
24
22
|
if (scriptLoadedRef.current || window.google?.maps) {
|
|
25
23
|
setIsScriptLoaded(true);
|
|
24
|
+
scriptLoadedRef.current = true;
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
|
-
// Check if script is already being loaded
|
|
29
27
|
const existingScript = document.querySelector('script[src*="maps.googleapis.com"]');
|
|
30
28
|
if (existingScript) {
|
|
31
|
-
// Wait for existing script to load
|
|
32
29
|
const checkLoaded = () => {
|
|
33
30
|
if (window.google?.maps?.places?.AutocompleteService) {
|
|
34
31
|
setIsScriptLoaded(true);
|
|
@@ -41,7 +38,6 @@ export function useGoogleAutocomplete(options) {
|
|
|
41
38
|
checkLoaded();
|
|
42
39
|
return;
|
|
43
40
|
}
|
|
44
|
-
// Create and inject the script
|
|
45
41
|
const script = document.createElement('script');
|
|
46
42
|
const params = new URLSearchParams({
|
|
47
43
|
key: apiKey,
|
|
@@ -63,12 +59,23 @@ export function useGoogleAutocomplete(options) {
|
|
|
63
59
|
// Failed to load Google Maps API
|
|
64
60
|
};
|
|
65
61
|
document.head.appendChild(script);
|
|
66
|
-
// Cleanup function
|
|
67
|
-
return () => {
|
|
68
|
-
// Note: We don't remove the script as it might be used by other components
|
|
69
|
-
// The script will remain in the DOM for the session
|
|
70
|
-
};
|
|
71
62
|
}, [apiKey, libraries, version, language, region]);
|
|
63
|
+
// Inject Google Maps script eagerly (unless defer is true)
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!apiKey || defer) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
injectScript();
|
|
69
|
+
}, [apiKey, defer, injectScript]);
|
|
70
|
+
// When script finishes loading in deferred mode, replay the pending search
|
|
71
|
+
const searchPlacesRef = useRef(null);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (isScriptLoaded && pendingSearchRef.current && searchPlacesRef.current) {
|
|
74
|
+
const { input, countryRestriction } = pendingSearchRef.current;
|
|
75
|
+
pendingSearchRef.current = null;
|
|
76
|
+
searchPlacesRef.current(input, countryRestriction);
|
|
77
|
+
}
|
|
78
|
+
}, [isScriptLoaded]);
|
|
72
79
|
// Initialize Google Places services
|
|
73
80
|
const initializeServices = useCallback(() => {
|
|
74
81
|
if (typeof window === 'undefined')
|
|
@@ -89,6 +96,11 @@ export function useGoogleAutocomplete(options) {
|
|
|
89
96
|
// Search for place predictions
|
|
90
97
|
const searchPlaces = useCallback((input, countryRestriction) => {
|
|
91
98
|
if (!isScriptLoaded) {
|
|
99
|
+
// When deferred, trigger lazy load and queue the search for replay
|
|
100
|
+
if (defer && !scriptLoadedRef.current) {
|
|
101
|
+
pendingSearchRef.current = { input, countryRestriction };
|
|
102
|
+
injectScript();
|
|
103
|
+
}
|
|
92
104
|
return;
|
|
93
105
|
}
|
|
94
106
|
if (!initializeServices()) {
|
|
@@ -124,7 +136,9 @@ export function useGoogleAutocomplete(options) {
|
|
|
124
136
|
}
|
|
125
137
|
});
|
|
126
138
|
}, 300); // 300ms debounce
|
|
127
|
-
}, [initializeServices, isScriptLoaded]);
|
|
139
|
+
}, [initializeServices, isScriptLoaded, defer, injectScript]);
|
|
140
|
+
// Keep ref in sync so the deferred-replay useEffect can call searchPlaces
|
|
141
|
+
searchPlacesRef.current = searchPlaces;
|
|
128
142
|
// Get detailed place information
|
|
129
143
|
const getPlaceDetails = useCallback((placeId) => {
|
|
130
144
|
return new Promise((resolve) => {
|
|
@@ -18,11 +18,8 @@ export interface UseISODataResult {
|
|
|
18
18
|
registeredLanguages: SupportedLanguage[];
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* React hook for accessing ISO3166 countries and regions data
|
|
22
|
-
*
|
|
23
|
-
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
24
|
-
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
25
|
-
* @returns Object with countries data and helper functions
|
|
21
|
+
* React hook for accessing ISO3166 countries and regions data.
|
|
22
|
+
* Fetches slim JSON from CDN on first use, then caches in memory.
|
|
26
23
|
*/
|
|
27
24
|
export declare function useISOData(language?: SupportedLanguage, autoImport?: boolean, disputeSetting?: string): UseISODataResult;
|
|
28
25
|
/**
|
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
import { useMemo, useEffect, useState, useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
import { getCountries, getStatesForCountry, importLanguage, isLanguageRegistered, getRegisteredLanguages } from '../../../data/iso3166';
|
|
2
|
+
import { getCountries, getStatesForCountry, ensureGeoDataLoaded, importLanguage, isLanguageRegistered, getRegisteredLanguages } from '../../../data/iso3166';
|
|
4
3
|
/**
|
|
5
|
-
* React hook for accessing ISO3166 countries and regions data
|
|
6
|
-
*
|
|
7
|
-
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
8
|
-
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
9
|
-
* @returns Object with countries data and helper functions
|
|
4
|
+
* React hook for accessing ISO3166 countries and regions data.
|
|
5
|
+
* Fetches slim JSON from CDN on first use, then caches in memory.
|
|
10
6
|
*/
|
|
11
7
|
export function useISOData(language = 'en', autoImport = true, disputeSetting = 'UN') {
|
|
12
8
|
const [isLanguageLoaded, setIsLanguageLoaded] = useState(isLanguageRegistered(language));
|
|
13
9
|
const [registeredLanguages, setRegisteredLanguages] = useState(getRegisteredLanguages);
|
|
14
|
-
//
|
|
10
|
+
// Fetch geodata from CDN (countries + regions) for the requested language
|
|
15
11
|
useEffect(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
let cancelled = false;
|
|
13
|
+
ensureGeoDataLoaded(language)
|
|
14
|
+
.then(() => {
|
|
15
|
+
if (!cancelled) {
|
|
19
16
|
setIsLanguageLoaded(true);
|
|
20
17
|
setRegisteredLanguages(getRegisteredLanguages());
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
.catch((err) => {
|
|
21
|
+
console.error('[SDK] Failed to load geodata from CDN:', err);
|
|
22
|
+
});
|
|
23
|
+
return () => { cancelled = true; };
|
|
25
24
|
}, [language, autoImport]);
|
|
26
25
|
const data = useMemo(() => {
|
|
26
|
+
if (!isLanguageLoaded) {
|
|
27
|
+
return {
|
|
28
|
+
countries: {},
|
|
29
|
+
getRegions: () => [],
|
|
30
|
+
findRegion: () => null,
|
|
31
|
+
mapGoogleToISO: () => null,
|
|
32
|
+
isLanguageLoaded: false,
|
|
33
|
+
registeredLanguages,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
27
36
|
try {
|
|
28
|
-
console.log('[SDK] Loading ISO data for language:', language, 'autoImport:', autoImport);
|
|
29
|
-
// Get countries from pre-built data with language support
|
|
30
37
|
const countriesArray = getCountries(language);
|
|
31
|
-
console.log('[SDK] Loaded countries count:', countriesArray.length);
|
|
32
|
-
// Transform to our expected format (Record<string, ISOCountry>)
|
|
33
38
|
const countries = {};
|
|
34
39
|
countriesArray.forEach((country) => {
|
|
35
40
|
countries[country.code] = {
|
|
@@ -39,7 +44,6 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
39
44
|
name: country.name,
|
|
40
45
|
};
|
|
41
46
|
});
|
|
42
|
-
// Helper to load regions for a specific country
|
|
43
47
|
const getRegions = (countryCode) => {
|
|
44
48
|
try {
|
|
45
49
|
const states = getStatesForCountry(countryCode, language);
|
|
@@ -49,31 +53,26 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
49
53
|
}));
|
|
50
54
|
}
|
|
51
55
|
catch {
|
|
52
|
-
return [];
|
|
56
|
+
return [];
|
|
53
57
|
}
|
|
54
58
|
};
|
|
55
|
-
// Find a specific region by ISO code
|
|
56
59
|
const findRegion = (countryCode, regionCode) => {
|
|
57
60
|
const regions = getRegions(countryCode);
|
|
58
61
|
return regions.find((region) => region.iso === regionCode) ?? null;
|
|
59
62
|
};
|
|
60
|
-
// Map Google Places state to ISO region (proven 100% success rate)
|
|
61
63
|
const mapGoogleToISO = (googleState, googleStateLong, countryCode) => {
|
|
62
64
|
const regions = getRegions(countryCode);
|
|
63
65
|
if (regions.length === 0)
|
|
64
66
|
return null;
|
|
65
|
-
// Strategy 1: Exact ISO code match (86% success rate)
|
|
66
67
|
let match = regions.find((r) => r.iso === googleState);
|
|
67
68
|
if (match)
|
|
68
69
|
return match;
|
|
69
|
-
// Strategy 2: Name matching (14% success rate)
|
|
70
70
|
match = regions.find((r) => r.name.toLowerCase() === googleState.toLowerCase());
|
|
71
71
|
if (match)
|
|
72
72
|
return match;
|
|
73
73
|
match = regions.find((r) => r.name.toLowerCase() === googleStateLong.toLowerCase());
|
|
74
74
|
if (match)
|
|
75
75
|
return match;
|
|
76
|
-
// Strategy 3: Partial name matching (fallback)
|
|
77
76
|
match = regions.find((r) => r.name.toLowerCase().includes(googleStateLong.toLowerCase()) ||
|
|
78
77
|
googleStateLong.toLowerCase().includes(r.name.toLowerCase()));
|
|
79
78
|
return match ?? null;
|
|
@@ -2,9 +2,9 @@ export interface Payment {
|
|
|
2
2
|
id: string;
|
|
3
3
|
status: string;
|
|
4
4
|
subStatus: string;
|
|
5
|
-
requireAction: 'none' | 'redirect' | 'redirect_to_payment' | 'error' | 'radar';
|
|
5
|
+
requireAction: 'none' | 'redirect' | 'redirect_to_payment' | 'error' | 'radar' | 'stripe_express_checkout';
|
|
6
6
|
requireActionData?: {
|
|
7
|
-
type: 'redirect' | 'redirect_to_payment' | 'threeds_auth' | 'processor_auth' | 'error' | 'stripe_radar' | 'finix_radar' | 'radar' | 'kesspay_auth' | 'trustflow_auth' | 'mastercard_auth';
|
|
7
|
+
type: 'redirect' | 'redirect_to_payment' | 'threeds_auth' | 'processor_auth' | 'error' | 'stripe_radar' | 'finix_radar' | 'radar' | 'kesspay_auth' | 'trustflow_auth' | 'mastercard_auth' | 'stripe_express_checkout';
|
|
8
8
|
url?: string;
|
|
9
9
|
processed: boolean;
|
|
10
10
|
processorId?: string;
|