@tagadapay/plugin-sdk 2.4.38 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/react/hooks/useCheckout.js +21 -33
- package/dist/react/hooks/useCheckoutSession.d.ts +19 -0
- package/dist/react/hooks/useCheckoutSession.js +108 -0
- package/dist/react/hooks/useCheckoutToken.d.ts +17 -0
- package/dist/react/hooks/useCheckoutToken.js +80 -0
- package/dist/react/hooks/useOrderBump.js +94 -29
- package/dist/react/hooks/useOrderBumpV2.d.ts +17 -0
- package/dist/react/hooks/useOrderBumpV2.js +95 -0
- package/dist/react/hooks/useOrderBumpV3.d.ts +23 -0
- package/dist/react/hooks/useOrderBumpV3.js +109 -0
- package/dist/react/hooks/usePayment.d.ts +1 -1
- package/dist/react/hooks/usePayment.js +2 -18
- package/dist/react/hooks/usePluginConfig.js +2 -13
- package/dist/react/hooks/usePostPurchases.js +11 -5
- package/dist/react/hooks/useProducts.js +2 -16
- package/dist/react/index.d.ts +9 -1
- package/dist/react/index.js +5 -1
- package/dist/react/providers/TagadaProvider.d.ts +0 -1
- package/dist/react/providers/TagadaProvider.js +16 -12
- package/dist/react/services/apiService.d.ts +1 -0
- package/dist/react/services/apiService.js +3 -0
- package/dist/v2/core/googleAutocomplete.d.ts +65 -0
- package/dist/v2/core/googleAutocomplete.js +94 -0
- package/dist/v2/core/index.d.ts +8 -0
- package/dist/v2/core/index.js +11 -0
- package/dist/v2/core/isoData.d.ts +50 -0
- package/dist/v2/core/isoData.js +103 -0
- package/dist/v2/core/resources/apiClient.d.ts +25 -0
- package/dist/v2/core/resources/apiClient.js +95 -0
- package/dist/v2/core/resources/checkout.d.ts +189 -0
- package/dist/v2/core/resources/checkout.js +119 -0
- package/dist/v2/core/resources/index.d.ts +13 -0
- package/dist/v2/core/resources/index.js +13 -0
- package/dist/v2/core/resources/offers.d.ts +98 -0
- package/dist/v2/core/resources/offers.js +115 -0
- package/dist/v2/core/resources/orders.d.ts +40 -0
- package/dist/v2/core/resources/orders.js +59 -0
- package/dist/v2/core/resources/payments.d.ts +140 -0
- package/dist/v2/core/resources/payments.js +126 -0
- package/dist/v2/core/resources/postPurchases.d.ts +182 -0
- package/dist/v2/core/resources/postPurchases.js +116 -0
- package/dist/v2/core/resources/products.d.ts +29 -0
- package/dist/v2/core/resources/products.js +49 -0
- package/dist/v2/core/resources/promotions.d.ts +45 -0
- package/dist/v2/core/resources/promotions.js +87 -0
- package/dist/v2/core/resources/threeds.d.ts +23 -0
- package/dist/v2/core/resources/threeds.js +15 -0
- package/dist/v2/core/utils/checkout.d.ts +24 -0
- package/dist/v2/core/utils/checkout.js +30 -0
- package/dist/v2/core/utils/currency.d.ts +28 -0
- package/dist/v2/core/utils/currency.js +272 -0
- package/dist/v2/core/utils/index.d.ts +12 -0
- package/dist/v2/core/utils/index.js +12 -0
- package/dist/v2/core/utils/order.d.ts +159 -0
- package/dist/v2/core/utils/order.js +42 -0
- package/dist/v2/core/utils/orderBump.d.ts +40 -0
- package/dist/v2/core/utils/orderBump.js +47 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +43 -0
- package/dist/v2/core/utils/pluginConfig.js +155 -0
- package/dist/v2/core/utils/postPurchases.d.ts +32 -0
- package/dist/v2/core/utils/postPurchases.js +42 -0
- package/dist/v2/core/utils/products.d.ts +58 -0
- package/dist/v2/core/utils/products.js +64 -0
- package/dist/v2/core/utils/promotions.d.ts +24 -0
- package/dist/v2/core/utils/promotions.js +30 -0
- package/dist/v2/index.d.ts +19 -0
- package/dist/v2/index.js +15 -0
- package/dist/v2/react/components/DebugDrawer.d.ts +7 -0
- package/dist/v2/react/components/DebugDrawer.js +383 -0
- package/dist/v2/react/hooks/useApiQuery.d.ts +28 -0
- package/dist/v2/react/hooks/useApiQuery.js +84 -0
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +39 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +208 -0
- package/dist/v2/react/hooks/useCheckoutToken.d.ts +17 -0
- package/dist/v2/react/hooks/useCheckoutToken.js +80 -0
- package/dist/v2/react/hooks/useCurrency.d.ts +9 -0
- package/dist/v2/react/hooks/useCurrency.js +21 -0
- package/dist/v2/react/hooks/useGeoLocation.d.ts +138 -0
- package/dist/v2/react/hooks/useGeoLocation.js +126 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +74 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +207 -0
- package/dist/v2/react/hooks/useISOData.d.ts +61 -0
- package/dist/v2/react/hooks/useISOData.js +176 -0
- package/dist/v2/react/hooks/useOffersQuery.d.ts +65 -0
- package/dist/v2/react/hooks/useOffersQuery.js +353 -0
- package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +20 -0
- package/dist/v2/react/hooks/useOrderBumpQuery.js +88 -0
- package/dist/v2/react/hooks/useOrderQuery.d.ts +29 -0
- package/dist/v2/react/hooks/useOrderQuery.js +98 -0
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +45 -0
- package/dist/v2/react/hooks/usePaymentPolling.js +153 -0
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +19 -0
- package/dist/v2/react/hooks/usePaymentQuery.js +283 -0
- package/dist/v2/react/hooks/usePluginConfig.d.ts +16 -0
- package/dist/v2/react/hooks/usePluginConfig.js +36 -0
- package/dist/v2/react/hooks/usePostPurchasesQuery.d.ts +63 -0
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +365 -0
- package/dist/v2/react/hooks/useProductsQuery.d.ts +31 -0
- package/dist/v2/react/hooks/useProductsQuery.js +102 -0
- package/dist/v2/react/hooks/usePromotionsQuery.d.ts +28 -0
- package/dist/v2/react/hooks/usePromotionsQuery.js +97 -0
- package/dist/v2/react/hooks/useThreeds.d.ts +36 -0
- package/dist/v2/react/hooks/useThreeds.js +166 -0
- package/dist/v2/react/hooks/useThreedsModal.d.ts +13 -0
- package/dist/v2/react/hooks/useThreedsModal.js +343 -0
- package/dist/v2/react/index.d.ts +38 -0
- package/dist/v2/react/index.js +27 -0
- package/dist/v2/react/providers/TagadaProvider.d.ts +63 -0
- package/dist/v2/react/providers/TagadaProvider.js +680 -0
- package/package.json +10 -3
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkout Hook using TanStack Query
|
|
3
|
+
* Replaces the coordinator pattern with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { useCallback, useState, useEffect, useMemo } from 'react';
|
|
6
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { CheckoutResource } from '../../core/resources/checkout';
|
|
8
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
10
|
+
import { useCurrency } from './useCurrency';
|
|
11
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
12
|
+
export function useCheckoutQuery(options = {}) {
|
|
13
|
+
const { checkoutToken: providedToken, enabled = true } = options;
|
|
14
|
+
const { storeId } = usePluginConfig();
|
|
15
|
+
const currency = useCurrency();
|
|
16
|
+
const queryClient = useQueryClient();
|
|
17
|
+
const { isSessionInitialized } = useTagadaContext();
|
|
18
|
+
// Create checkout resource client
|
|
19
|
+
const checkoutResource = useMemo(() => {
|
|
20
|
+
try {
|
|
21
|
+
return new CheckoutResource(getGlobalApiClient());
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error('Failed to initialize checkout resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
// Internal token state that can be updated after init
|
|
28
|
+
const [internalToken, setInternalToken] = useState(providedToken);
|
|
29
|
+
// Update internal token when provided token changes
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (providedToken && providedToken !== internalToken) {
|
|
32
|
+
setInternalToken(providedToken);
|
|
33
|
+
}
|
|
34
|
+
}, [providedToken, internalToken]);
|
|
35
|
+
// Use provided token or internal token
|
|
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
|
+
// Main checkout query
|
|
46
|
+
const { data: checkout, isLoading, error, isSuccess, refetch, } = useQuery({
|
|
47
|
+
queryKey: ['checkout', checkoutToken, currency.code],
|
|
48
|
+
queryFn: () => checkoutResource.getCheckout(checkoutToken, currency.code),
|
|
49
|
+
enabled: enabled && !!checkoutToken && isSessionInitialized,
|
|
50
|
+
staleTime: 30000, // 30 seconds
|
|
51
|
+
refetchOnWindowFocus: false,
|
|
52
|
+
});
|
|
53
|
+
console.log('🔍 [useCheckoutQuery] Query result:', {
|
|
54
|
+
hasCheckout: !!checkout,
|
|
55
|
+
isLoading,
|
|
56
|
+
error: error?.message,
|
|
57
|
+
isSuccess
|
|
58
|
+
});
|
|
59
|
+
// Refresh function
|
|
60
|
+
const refresh = useCallback(async () => {
|
|
61
|
+
if (checkoutToken) {
|
|
62
|
+
await refetch();
|
|
63
|
+
}
|
|
64
|
+
}, [refetch, checkoutToken]);
|
|
65
|
+
// Initialize checkout mutation
|
|
66
|
+
const initMutation = useMutation({
|
|
67
|
+
mutationFn: (params) => {
|
|
68
|
+
const requestBody = {
|
|
69
|
+
...params,
|
|
70
|
+
storeId: params.storeId || storeId,
|
|
71
|
+
returnUrl: params.returnUrl || window.location.origin,
|
|
72
|
+
customer: {
|
|
73
|
+
...params.customer,
|
|
74
|
+
currency: params.customer?.currency ?? currency.code,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
return checkoutResource.initCheckout(requestBody);
|
|
78
|
+
},
|
|
79
|
+
onSuccess: (response) => {
|
|
80
|
+
// Update URL with checkout token
|
|
81
|
+
if (typeof window !== 'undefined' && response.checkoutToken) {
|
|
82
|
+
const currentUrl = new URL(window.location.href);
|
|
83
|
+
if (!currentUrl.searchParams.has('checkoutToken')) {
|
|
84
|
+
const newUrl = new URL(window.location.href);
|
|
85
|
+
newUrl.searchParams.set('checkoutToken', response.checkoutToken);
|
|
86
|
+
window.history.replaceState(null, '', newUrl.toString());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// Order bump functionality removed - use useOrderBumpQuery instead
|
|
92
|
+
// Line items mutation
|
|
93
|
+
const lineItemsMutation = useMutation({
|
|
94
|
+
mutationFn: ({ lineItems }) => {
|
|
95
|
+
if (!checkout?.checkoutSession?.id) {
|
|
96
|
+
throw new Error('No checkout session available');
|
|
97
|
+
}
|
|
98
|
+
return checkoutResource.updateLineItems(checkout.checkoutSession.id, lineItems);
|
|
99
|
+
},
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
if (checkoutToken) {
|
|
102
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// Item quantity mutation
|
|
107
|
+
const quantityMutation = useMutation({
|
|
108
|
+
mutationFn: ({ variantId, quantity, priceId }) => {
|
|
109
|
+
if (!checkout?.checkoutSession?.id) {
|
|
110
|
+
throw new Error('No checkout session available');
|
|
111
|
+
}
|
|
112
|
+
return checkoutResource.setItemQuantity(checkout.checkoutSession.id, variantId, quantity, priceId);
|
|
113
|
+
},
|
|
114
|
+
onSuccess: () => {
|
|
115
|
+
if (checkoutToken) {
|
|
116
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
// Customer update mutation
|
|
121
|
+
const customerMutation = useMutation({
|
|
122
|
+
mutationFn: (data) => {
|
|
123
|
+
if (!checkout?.checkoutSession?.id) {
|
|
124
|
+
throw new Error('No checkout session available');
|
|
125
|
+
}
|
|
126
|
+
return checkoutResource.updateCustomer(checkout.checkoutSession.id, data);
|
|
127
|
+
},
|
|
128
|
+
onSuccess: () => {
|
|
129
|
+
if (checkoutToken) {
|
|
130
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// Customer and session info update mutation
|
|
135
|
+
const customerAndSessionMutation = useMutation({
|
|
136
|
+
mutationFn: (data) => {
|
|
137
|
+
if (!checkout?.checkoutSession?.id) {
|
|
138
|
+
throw new Error('No checkout session available');
|
|
139
|
+
}
|
|
140
|
+
return checkoutResource.updateCustomerAndSessionInfo(checkout.checkoutSession.id, data);
|
|
141
|
+
},
|
|
142
|
+
onSuccess: () => {
|
|
143
|
+
if (checkoutToken) {
|
|
144
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
// Promotion code mutation
|
|
149
|
+
const promotionMutation = useMutation({
|
|
150
|
+
mutationFn: ({ code }) => {
|
|
151
|
+
if (!checkout?.checkoutSession?.id) {
|
|
152
|
+
throw new Error('No checkout session available');
|
|
153
|
+
}
|
|
154
|
+
return checkoutResource.applyPromotionCode(checkout.checkoutSession.id, code);
|
|
155
|
+
},
|
|
156
|
+
onSuccess: () => {
|
|
157
|
+
if (checkoutToken) {
|
|
158
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
159
|
+
}
|
|
160
|
+
if (checkout?.checkoutSession?.id) {
|
|
161
|
+
void queryClient.invalidateQueries({ queryKey: ['promotions', checkout.checkoutSession.id] });
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Remove promotion mutation
|
|
166
|
+
const removePromotionMutation = useMutation({
|
|
167
|
+
mutationFn: ({ promotionId }) => {
|
|
168
|
+
if (!checkout?.checkoutSession?.id) {
|
|
169
|
+
throw new Error('No checkout session available');
|
|
170
|
+
}
|
|
171
|
+
return checkoutResource.removePromotion(checkout.checkoutSession.id, promotionId);
|
|
172
|
+
},
|
|
173
|
+
onSuccess: () => {
|
|
174
|
+
if (checkoutToken) {
|
|
175
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
176
|
+
}
|
|
177
|
+
if (checkout?.checkoutSession?.id) {
|
|
178
|
+
void queryClient.invalidateQueries({ queryKey: ['promotions', checkout.checkoutSession.id] });
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
// Query data
|
|
184
|
+
checkout,
|
|
185
|
+
isLoading,
|
|
186
|
+
error,
|
|
187
|
+
isSuccess,
|
|
188
|
+
// Actions
|
|
189
|
+
init: async (params) => {
|
|
190
|
+
const result = await initMutation.mutateAsync(params);
|
|
191
|
+
// Update internal token state so the query can fetch the checkout data
|
|
192
|
+
setInternalToken(result.checkoutToken);
|
|
193
|
+
return {
|
|
194
|
+
checkoutUrl: result.checkoutUrl,
|
|
195
|
+
checkoutSession: checkout?.checkoutSession ?? {},
|
|
196
|
+
checkoutToken: result.checkoutToken,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
refresh,
|
|
200
|
+
// Checkout operations
|
|
201
|
+
updateLineItems: (lineItems) => lineItemsMutation.mutateAsync({ lineItems }),
|
|
202
|
+
setItemQuantity: (variantId, quantity, priceId) => quantityMutation.mutateAsync({ variantId, quantity, priceId }),
|
|
203
|
+
updateCustomer: (data) => customerMutation.mutateAsync(data),
|
|
204
|
+
updateCustomerAndSessionInfo: (data) => customerAndSessionMutation.mutateAsync(data),
|
|
205
|
+
applyPromotionCode: (code) => promotionMutation.mutateAsync({ code }),
|
|
206
|
+
removePromotion: (promotionId) => removePromotionMutation.mutateAsync({ promotionId }),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface UseCheckoutTokenOptions {
|
|
2
|
+
checkoutToken?: string;
|
|
3
|
+
autoLoadFromToken?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface UseCheckoutTokenResult {
|
|
6
|
+
checkoutToken: string | null;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: Error | null;
|
|
9
|
+
isInitialized: boolean;
|
|
10
|
+
setToken: (token: string | null) => void;
|
|
11
|
+
clearToken: () => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* React hook for managing checkout token state
|
|
15
|
+
* Handles token management without fetching checkout data
|
|
16
|
+
*/
|
|
17
|
+
export declare function useCheckoutToken(options?: UseCheckoutTokenOptions): UseCheckoutTokenResult;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
|
+
/**
|
|
4
|
+
* React hook for managing checkout token state
|
|
5
|
+
* Handles token management without fetching checkout data
|
|
6
|
+
*/
|
|
7
|
+
export function useCheckoutToken(options = {}) {
|
|
8
|
+
const { isSessionInitialized } = useTagadaContext();
|
|
9
|
+
const { checkoutToken: providedToken, autoLoadFromToken = true } = options;
|
|
10
|
+
const [checkoutToken, setCheckoutToken] = useState(providedToken || null);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
14
|
+
const hasAutoLoadedRef = useRef(false);
|
|
15
|
+
const isSessionInitializedRef = useRef(isSessionInitialized);
|
|
16
|
+
// Keep ref in sync with state
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
isSessionInitializedRef.current = isSessionInitialized;
|
|
19
|
+
}, [isSessionInitialized]);
|
|
20
|
+
// Update token when provided token changes
|
|
21
|
+
useEffect(() => {
|
|
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
|
+
setCheckoutToken(providedToken);
|
|
28
|
+
setIsInitialized(false);
|
|
29
|
+
hasAutoLoadedRef.current = false;
|
|
30
|
+
}
|
|
31
|
+
}, [providedToken, checkoutToken]);
|
|
32
|
+
// Auto-load token from URL if no token provided
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!providedToken && autoLoadFromToken && !checkoutToken && !hasAutoLoadedRef.current) {
|
|
35
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
36
|
+
const urlToken = urlParams.get('checkoutToken') || urlParams.get('token');
|
|
37
|
+
if (urlToken) {
|
|
38
|
+
console.log('🔧 useCheckoutToken: Auto-loading token from URL', {
|
|
39
|
+
tokenPreview: urlToken.substring(0, 8) + '...'
|
|
40
|
+
});
|
|
41
|
+
setCheckoutToken(urlToken);
|
|
42
|
+
hasAutoLoadedRef.current = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [providedToken, autoLoadFromToken, checkoutToken]);
|
|
46
|
+
// Wait for session initialization
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isSessionInitialized || !checkoutToken || isInitialized) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log('🔧 useCheckoutToken: Session initialized, token ready', {
|
|
52
|
+
tokenPreview: checkoutToken.substring(0, 8) + '...',
|
|
53
|
+
isSessionInitialized
|
|
54
|
+
});
|
|
55
|
+
setIsInitialized(true);
|
|
56
|
+
}, [isSessionInitialized, checkoutToken, isInitialized]);
|
|
57
|
+
const setToken = useCallback((token) => {
|
|
58
|
+
console.log('🔧 useCheckoutToken: Setting token', {
|
|
59
|
+
tokenPreview: token ? token.substring(0, 8) + '...' : null
|
|
60
|
+
});
|
|
61
|
+
setCheckoutToken(token);
|
|
62
|
+
setIsInitialized(!!token);
|
|
63
|
+
setError(null);
|
|
64
|
+
}, []);
|
|
65
|
+
const clearToken = useCallback(() => {
|
|
66
|
+
console.log('🔧 useCheckoutToken: Clearing token');
|
|
67
|
+
setCheckoutToken(null);
|
|
68
|
+
setIsInitialized(false);
|
|
69
|
+
setError(null);
|
|
70
|
+
hasAutoLoadedRef.current = false;
|
|
71
|
+
}, []);
|
|
72
|
+
return {
|
|
73
|
+
checkoutToken,
|
|
74
|
+
isLoading,
|
|
75
|
+
error,
|
|
76
|
+
isInitialized,
|
|
77
|
+
setToken,
|
|
78
|
+
clearToken,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency Hook
|
|
3
|
+
* Uses CurrencyCore for business logic
|
|
4
|
+
*/
|
|
5
|
+
import { Currency } from '../../core/utils/currency';
|
|
6
|
+
export interface UseCurrencyResult extends Currency {
|
|
7
|
+
format: (amount: number) => string;
|
|
8
|
+
}
|
|
9
|
+
export declare function useCurrency(defaultCurrency?: string): UseCurrencyResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency Hook
|
|
3
|
+
* Uses CurrencyCore for business logic
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
7
|
+
import { CurrencyUtils } from '../../core/utils/currency';
|
|
8
|
+
export function useCurrency(defaultCurrency = 'USD') {
|
|
9
|
+
const context = useTagadaContext();
|
|
10
|
+
const currency = useMemo(() => {
|
|
11
|
+
return CurrencyUtils.getCurrency(context, defaultCurrency);
|
|
12
|
+
}, [context, defaultCurrency]);
|
|
13
|
+
const format = (amount) => {
|
|
14
|
+
const symbol = currency.symbol || currency.code;
|
|
15
|
+
return `${symbol}${amount.toFixed(currency.decimalPlaces)}`;
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
...currency,
|
|
19
|
+
format,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export interface GeoLocationData {
|
|
2
|
+
ip_address?: string | null;
|
|
3
|
+
city?: string | null;
|
|
4
|
+
city_geoname_id?: number | null;
|
|
5
|
+
region?: string | null;
|
|
6
|
+
region_iso_code?: string | null;
|
|
7
|
+
region_geoname_id?: number | null;
|
|
8
|
+
postal_code?: string | null;
|
|
9
|
+
country?: string | null;
|
|
10
|
+
country_code?: string | null;
|
|
11
|
+
country_geoname_id?: number | null;
|
|
12
|
+
country_is_eu?: boolean | null;
|
|
13
|
+
continent?: string | null;
|
|
14
|
+
continent_code?: string | null;
|
|
15
|
+
continent_geoname_id?: number | null;
|
|
16
|
+
latitude?: number | null;
|
|
17
|
+
longitude?: number | null;
|
|
18
|
+
security?: {
|
|
19
|
+
is_vpn?: boolean | null;
|
|
20
|
+
} | null;
|
|
21
|
+
timezone?: {
|
|
22
|
+
name?: string | null;
|
|
23
|
+
abbreviation?: string | null;
|
|
24
|
+
gmt_offset?: number | null;
|
|
25
|
+
current_time?: string | null;
|
|
26
|
+
is_dst?: boolean | null;
|
|
27
|
+
} | null;
|
|
28
|
+
currency?: {
|
|
29
|
+
currency_name?: string | null;
|
|
30
|
+
currency_code?: string | null;
|
|
31
|
+
} | null;
|
|
32
|
+
connection?: {
|
|
33
|
+
autonomous_system_number?: number | null;
|
|
34
|
+
autonomous_system_organization?: string | null;
|
|
35
|
+
connection_type?: string | null;
|
|
36
|
+
isp_name?: string | null;
|
|
37
|
+
organization_name?: string | null;
|
|
38
|
+
} | null;
|
|
39
|
+
flag?: {
|
|
40
|
+
emoji?: string | null;
|
|
41
|
+
unicode?: string | null;
|
|
42
|
+
png?: string | null;
|
|
43
|
+
svg?: string | null;
|
|
44
|
+
} | null;
|
|
45
|
+
as?: string | null;
|
|
46
|
+
isp?: string | null;
|
|
47
|
+
lat?: number | null;
|
|
48
|
+
lon?: number | null;
|
|
49
|
+
org?: string | null;
|
|
50
|
+
query?: string | null;
|
|
51
|
+
regionName?: string | null;
|
|
52
|
+
status?: string | null;
|
|
53
|
+
zip?: string | null;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface UseGeoLocationOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Whether to automatically fetch geolocation data on mount
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
autoFetch?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Custom IP address to fetch geolocation for
|
|
64
|
+
* If not provided, will use the client's IP
|
|
65
|
+
*/
|
|
66
|
+
ip?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Whether to refetch data when the hook mounts
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
refetchOnMount?: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface UseGeoLocationReturn {
|
|
74
|
+
/**
|
|
75
|
+
* The geolocation data
|
|
76
|
+
*/
|
|
77
|
+
data: GeoLocationData | null;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the request is currently loading
|
|
80
|
+
*/
|
|
81
|
+
isLoading: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Any error that occurred during the request
|
|
84
|
+
*/
|
|
85
|
+
error: string | null;
|
|
86
|
+
/**
|
|
87
|
+
* Function to manually fetch geolocation data
|
|
88
|
+
*/
|
|
89
|
+
fetchGeoData: (ip?: string) => Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Function to clear the current data and error state
|
|
92
|
+
*/
|
|
93
|
+
clearData: () => void;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the data is from localhost/development
|
|
96
|
+
*/
|
|
97
|
+
isLocalhost: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the current IP is valid
|
|
100
|
+
*/
|
|
101
|
+
isValidIP: boolean;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Hook to fetch and manage geolocation data - V2 Implementation
|
|
105
|
+
* Compatible with V1 interface while using V2 provider architecture
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* function MyComponent() {
|
|
110
|
+
* const { data, isLoading, error, fetchGeoData } = useGeoLocation();
|
|
111
|
+
*
|
|
112
|
+
* if (isLoading) return <div>Loading location...</div>;
|
|
113
|
+
* if (error) return <div>Error: {error}</div>;
|
|
114
|
+
*
|
|
115
|
+
* return (
|
|
116
|
+
* <div>
|
|
117
|
+
* <p>Country: {data?.country}</p>
|
|
118
|
+
* <p>City: {data?.city}</p>
|
|
119
|
+
* <p>IP: {data?.ip_address}</p>
|
|
120
|
+
* </div>
|
|
121
|
+
* );
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```tsx
|
|
127
|
+
* function MyComponent() {
|
|
128
|
+
* const { fetchGeoData } = useGeoLocation({ autoFetch: false });
|
|
129
|
+
*
|
|
130
|
+
* const handleClick = () => {
|
|
131
|
+
* fetchGeoData('8.8.8.8'); // Fetch for specific IP
|
|
132
|
+
* };
|
|
133
|
+
*
|
|
134
|
+
* return <button onClick={handleClick}>Get Location for Google DNS</button>;
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function useGeoLocation(options?: UseGeoLocationOptions): UseGeoLocationReturn;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to fetch and manage geolocation data - V2 Implementation
|
|
6
|
+
* Compatible with V1 interface while using V2 provider architecture
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function MyComponent() {
|
|
11
|
+
* const { data, isLoading, error, fetchGeoData } = useGeoLocation();
|
|
12
|
+
*
|
|
13
|
+
* if (isLoading) return <div>Loading location...</div>;
|
|
14
|
+
* if (error) return <div>Error: {error}</div>;
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <div>
|
|
18
|
+
* <p>Country: {data?.country}</p>
|
|
19
|
+
* <p>City: {data?.city}</p>
|
|
20
|
+
* <p>IP: {data?.ip_address}</p>
|
|
21
|
+
* </div>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* function MyComponent() {
|
|
29
|
+
* const { fetchGeoData } = useGeoLocation({ autoFetch: false });
|
|
30
|
+
*
|
|
31
|
+
* const handleClick = () => {
|
|
32
|
+
* fetchGeoData('8.8.8.8'); // Fetch for specific IP
|
|
33
|
+
* };
|
|
34
|
+
*
|
|
35
|
+
* return <button onClick={handleClick}>Get Location for Google DNS</button>;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function useGeoLocation(options = {}) {
|
|
40
|
+
const { autoFetch = true, ip, refetchOnMount = false } = options;
|
|
41
|
+
const { apiService } = useTagadaContext();
|
|
42
|
+
const [data, setData] = useState(null);
|
|
43
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
44
|
+
const [error, setError] = useState(null);
|
|
45
|
+
// Helper function to check if IP is localhost
|
|
46
|
+
const isLocalhost = useCallback((ipAddress) => {
|
|
47
|
+
if (!ipAddress)
|
|
48
|
+
return false;
|
|
49
|
+
return ipAddress === '127.0.0.1' || ipAddress === '::1' || ipAddress === 'localhost';
|
|
50
|
+
}, []);
|
|
51
|
+
// Helper function to validate IP address format
|
|
52
|
+
const isValidIP = useCallback((ipAddress) => {
|
|
53
|
+
if (!ipAddress)
|
|
54
|
+
return false;
|
|
55
|
+
// IPv4 regex pattern
|
|
56
|
+
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
57
|
+
// IPv6 regex pattern (simplified)
|
|
58
|
+
const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
|
59
|
+
if (!ipv4Pattern.test(ipAddress) && !ipv6Pattern.test(ipAddress)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// Additional validation for IPv4
|
|
63
|
+
if (ipv4Pattern.test(ipAddress)) {
|
|
64
|
+
const parts = ipAddress.split('.');
|
|
65
|
+
return parts.every((part) => {
|
|
66
|
+
const num = parseInt(part, 10);
|
|
67
|
+
return num >= 0 && num <= 255;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}, []);
|
|
72
|
+
// Function to fetch geolocation data
|
|
73
|
+
const fetchGeoData = useCallback(async (customIp) => {
|
|
74
|
+
if (!apiService) {
|
|
75
|
+
setError('API service not available');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setIsLoading(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
try {
|
|
81
|
+
const targetIp = customIp || ip;
|
|
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
|
+
const geoData = await apiService.fetch(endpoint, {
|
|
85
|
+
method: 'GET',
|
|
86
|
+
skipAuth: true, // Geolocation endpoint doesn't require authentication
|
|
87
|
+
});
|
|
88
|
+
setData(geoData);
|
|
89
|
+
console.log('[SDK] Geolocation data fetched successfully:', geoData);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch geolocation data';
|
|
93
|
+
setError(errorMessage);
|
|
94
|
+
console.error('[SDK] Error fetching geolocation data:', err);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
setIsLoading(false);
|
|
98
|
+
}
|
|
99
|
+
}, [apiService, ip]);
|
|
100
|
+
// Function to clear data and error state
|
|
101
|
+
const clearData = useCallback(() => {
|
|
102
|
+
setData(null);
|
|
103
|
+
setError(null);
|
|
104
|
+
}, []);
|
|
105
|
+
// Auto-fetch on mount if enabled
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (autoFetch && apiService) {
|
|
108
|
+
void fetchGeoData();
|
|
109
|
+
}
|
|
110
|
+
}, [autoFetch, apiService, fetchGeoData]);
|
|
111
|
+
// Refetch on mount if enabled
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (refetchOnMount && apiService) {
|
|
114
|
+
void fetchGeoData();
|
|
115
|
+
}
|
|
116
|
+
}, [refetchOnMount, apiService, fetchGeoData]);
|
|
117
|
+
return {
|
|
118
|
+
data,
|
|
119
|
+
isLoading,
|
|
120
|
+
error,
|
|
121
|
+
fetchGeoData,
|
|
122
|
+
clearData,
|
|
123
|
+
isLocalhost: isLocalhost(data?.ip_address),
|
|
124
|
+
isValidIP: isValidIP(data?.ip_address),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Autocomplete Hook - V2 Implementation
|
|
3
|
+
* Compatible with V1 interface while using V2 core architecture
|
|
4
|
+
*/
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
google?: {
|
|
8
|
+
maps?: {
|
|
9
|
+
places?: {
|
|
10
|
+
AutocompleteService: new () => any;
|
|
11
|
+
PlacesService: new (map: any) => any;
|
|
12
|
+
PlacesServiceStatus: {
|
|
13
|
+
OK: string;
|
|
14
|
+
ZERO_RESULTS: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
Map: new (element: HTMLElement) => any;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export interface GooglePrediction {
|
|
23
|
+
place_id: string;
|
|
24
|
+
description: string;
|
|
25
|
+
structured_formatting?: {
|
|
26
|
+
main_text: string;
|
|
27
|
+
secondary_text: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface GooglePlaceDetails {
|
|
31
|
+
place_id: string;
|
|
32
|
+
formatted_address: string;
|
|
33
|
+
address_components: {
|
|
34
|
+
long_name: string;
|
|
35
|
+
short_name: string;
|
|
36
|
+
types: string[];
|
|
37
|
+
}[];
|
|
38
|
+
geometry?: {
|
|
39
|
+
location: {
|
|
40
|
+
lat: number;
|
|
41
|
+
lng: number;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface ExtractedAddress {
|
|
46
|
+
streetNumber: string;
|
|
47
|
+
route: string;
|
|
48
|
+
locality: string;
|
|
49
|
+
administrativeAreaLevel1: string;
|
|
50
|
+
administrativeAreaLevel1Long: string;
|
|
51
|
+
country: string;
|
|
52
|
+
postalCode: string;
|
|
53
|
+
}
|
|
54
|
+
export interface UseGoogleAutocompleteOptions {
|
|
55
|
+
apiKey: string;
|
|
56
|
+
libraries?: string[];
|
|
57
|
+
version?: string;
|
|
58
|
+
language?: string;
|
|
59
|
+
region?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface UseGoogleAutocompleteResult {
|
|
62
|
+
predictions: GooglePrediction[];
|
|
63
|
+
isLoading: boolean;
|
|
64
|
+
isScriptLoaded: boolean;
|
|
65
|
+
searchPlaces: (input: string, countryRestriction?: string) => void;
|
|
66
|
+
getPlaceDetails: (placeId: string) => Promise<GooglePlaceDetails | null>;
|
|
67
|
+
extractAddressComponents: (place: GooglePlaceDetails) => ExtractedAddress;
|
|
68
|
+
clearPredictions: () => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* React hook for Google Places Autocomplete with automatic script injection
|
|
72
|
+
* Automatically loads the Google Maps JavaScript API with Places library
|
|
73
|
+
*/
|
|
74
|
+
export declare function useGoogleAutocomplete(options: UseGoogleAutocompleteOptions): UseGoogleAutocompleteResult;
|