@tagadapay/plugin-sdk 2.4.39 → 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 +19 -2
- 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 +92 -13
- 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/usePostPurchases.js +11 -5
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +4 -0
- 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,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offers Hook using TanStack Query
|
|
3
|
+
* Handles offers with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo, useCallback, useState } from 'react';
|
|
6
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { OffersResource } from '../../core/resources/offers';
|
|
8
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
10
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
11
|
+
export function useOffersQuery(options = {}) {
|
|
12
|
+
const { offerIds = [], enabled = true, returnUrl } = options;
|
|
13
|
+
const { storeId } = usePluginConfig();
|
|
14
|
+
const { isSessionInitialized, session } = useTagadaContext();
|
|
15
|
+
const _queryClient = useQueryClient();
|
|
16
|
+
// State for checkout sessions per offer (similar to postPurchases)
|
|
17
|
+
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
18
|
+
// Create offers resource client
|
|
19
|
+
const offersResource = useMemo(() => {
|
|
20
|
+
try {
|
|
21
|
+
return new OffersResource(getGlobalApiClient());
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error('Failed to initialize offers resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
// Helper function for fetching order summary (similar to postPurchases)
|
|
28
|
+
const fetchOrderSummary = useCallback(async (offerId, sessionId) => {
|
|
29
|
+
try {
|
|
30
|
+
// Set updating state
|
|
31
|
+
setCheckoutSessions(prev => ({
|
|
32
|
+
...prev,
|
|
33
|
+
[offerId]: {
|
|
34
|
+
...prev[offerId],
|
|
35
|
+
isUpdatingSummary: true,
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
const summaryResult = await offersResource.getOrderSummary(sessionId, true);
|
|
39
|
+
if (summaryResult) {
|
|
40
|
+
// Sort items by productId to ensure consistent order
|
|
41
|
+
const sortedItems = [...summaryResult.items].sort((a, b) => a.productId.localeCompare(b.productId));
|
|
42
|
+
const orderSummary = {
|
|
43
|
+
...summaryResult,
|
|
44
|
+
items: sortedItems,
|
|
45
|
+
};
|
|
46
|
+
// Initialize selected variants based on the summary
|
|
47
|
+
const initialVariants = {};
|
|
48
|
+
sortedItems.forEach((item) => {
|
|
49
|
+
if (item.productId && item.variantId) {
|
|
50
|
+
initialVariants[item.productId] = item.variantId;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
setCheckoutSessions(prev => ({
|
|
54
|
+
...prev,
|
|
55
|
+
[offerId]: {
|
|
56
|
+
...prev[offerId],
|
|
57
|
+
orderSummary,
|
|
58
|
+
selectedVariants: initialVariants,
|
|
59
|
+
isUpdatingSummary: false,
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`[SDK] Failed to fetch order summary for offer ${offerId}:`, error);
|
|
66
|
+
setCheckoutSessions(prev => ({
|
|
67
|
+
...prev,
|
|
68
|
+
[offerId]: {
|
|
69
|
+
...prev[offerId],
|
|
70
|
+
isUpdatingSummary: false,
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}, [offersResource]);
|
|
76
|
+
// Create query key based on options
|
|
77
|
+
const queryKey = useMemo(() => ['offers', { storeId, offerIds }], [storeId, offerIds]);
|
|
78
|
+
// Use TanStack Query for fetching offers
|
|
79
|
+
const { data: offers = [], isLoading, error, refetch } = useQuery({
|
|
80
|
+
queryKey,
|
|
81
|
+
enabled: enabled && !!storeId && isSessionInitialized,
|
|
82
|
+
queryFn: async () => {
|
|
83
|
+
if (!storeId) {
|
|
84
|
+
throw new Error('Store ID not found. Make sure the TagadaProvider is properly configured.');
|
|
85
|
+
}
|
|
86
|
+
if (offerIds.length > 0) {
|
|
87
|
+
// Fetch specific offers by IDs
|
|
88
|
+
return await offersResource.getOffersByIds(storeId, offerIds);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Fetch all offers for the store
|
|
92
|
+
return await offersResource.getOffers(storeId);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
96
|
+
refetchOnWindowFocus: false,
|
|
97
|
+
});
|
|
98
|
+
// Mutations for offer actions
|
|
99
|
+
const createCheckoutSessionMutation = useMutation({
|
|
100
|
+
mutationFn: async ({ offerId, returnUrl }) => {
|
|
101
|
+
return await offersResource.createCheckoutSession(offerId, returnUrl);
|
|
102
|
+
},
|
|
103
|
+
onError: (error) => {
|
|
104
|
+
console.error('Failed to create checkout session:', error);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const payOfferMutation = useMutation({
|
|
108
|
+
mutationFn: async ({ offerId, orderId }) => {
|
|
109
|
+
return await offersResource.payOffer(offerId, orderId);
|
|
110
|
+
},
|
|
111
|
+
onError: (error) => {
|
|
112
|
+
console.error('Failed to pay offer:', error);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const transformToCheckoutMutation = useMutation({
|
|
116
|
+
mutationFn: async ({ offerId, returnUrl }) => {
|
|
117
|
+
return await offersResource.transformToCheckout(offerId, returnUrl);
|
|
118
|
+
},
|
|
119
|
+
onError: (error) => {
|
|
120
|
+
console.error('Failed to transform offer to checkout:', error);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
const payWithCheckoutSessionMutation = useMutation({
|
|
124
|
+
mutationFn: async ({ checkoutSessionId, orderId }) => {
|
|
125
|
+
return await offersResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
126
|
+
},
|
|
127
|
+
onError: (error) => {
|
|
128
|
+
console.error('Failed to pay with checkout session:', error);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
const initCheckoutSessionMutation = useMutation({
|
|
132
|
+
mutationFn: async ({ offerId, orderId, customerId }) => {
|
|
133
|
+
return await offersResource.initCheckoutSession(offerId, orderId, customerId);
|
|
134
|
+
},
|
|
135
|
+
onError: (error) => {
|
|
136
|
+
console.error('Failed to initialize checkout session:', error);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
// Helper functions (matching V1 useOffers exactly)
|
|
140
|
+
const getOffer = useCallback((offerId) => {
|
|
141
|
+
return offers.find((offer) => offer.id === offerId);
|
|
142
|
+
}, [offers]);
|
|
143
|
+
const getTotalValue = useCallback(() => {
|
|
144
|
+
return offers.reduce((total, offer) => {
|
|
145
|
+
const firstSummary = offer.summaries[0];
|
|
146
|
+
return total + (firstSummary?.totalAdjustedAmount || 0);
|
|
147
|
+
}, 0);
|
|
148
|
+
}, [offers]);
|
|
149
|
+
const getTotalSavings = useCallback(() => {
|
|
150
|
+
return offers.reduce((total, offer) => {
|
|
151
|
+
const firstSummary = offer.summaries[0];
|
|
152
|
+
return total + (firstSummary?.totalPromotionAmount || 0);
|
|
153
|
+
}, 0);
|
|
154
|
+
}, [offers]);
|
|
155
|
+
// Action functions
|
|
156
|
+
const createCheckoutSession = useCallback(async (offerId, options) => {
|
|
157
|
+
return await createCheckoutSessionMutation.mutateAsync({
|
|
158
|
+
offerId,
|
|
159
|
+
returnUrl: options?.returnUrl || returnUrl,
|
|
160
|
+
});
|
|
161
|
+
}, [createCheckoutSessionMutation, returnUrl]);
|
|
162
|
+
const payOffer = useCallback(async (offerId, orderId) => {
|
|
163
|
+
return await payOfferMutation.mutateAsync({ offerId, orderId });
|
|
164
|
+
}, [payOfferMutation]);
|
|
165
|
+
const transformToCheckout = useCallback(async (offerId, options) => {
|
|
166
|
+
return await transformToCheckoutMutation.mutateAsync({
|
|
167
|
+
offerId,
|
|
168
|
+
returnUrl: options?.returnUrl || returnUrl,
|
|
169
|
+
});
|
|
170
|
+
}, [transformToCheckoutMutation, returnUrl]);
|
|
171
|
+
const payWithCheckoutSession = useCallback(async (checkoutSessionId, orderId) => {
|
|
172
|
+
return await payWithCheckoutSessionMutation.mutateAsync({ checkoutSessionId, orderId });
|
|
173
|
+
}, [payWithCheckoutSessionMutation]);
|
|
174
|
+
const initCheckoutSession = useCallback(async (offerId, orderId, customerId) => {
|
|
175
|
+
// Use customerId from session context if not provided
|
|
176
|
+
const effectiveCustomerId = customerId || session?.customerId;
|
|
177
|
+
if (!effectiveCustomerId) {
|
|
178
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
179
|
+
}
|
|
180
|
+
return await initCheckoutSessionMutation.mutateAsync({
|
|
181
|
+
offerId,
|
|
182
|
+
orderId,
|
|
183
|
+
customerId: effectiveCustomerId
|
|
184
|
+
});
|
|
185
|
+
}, [initCheckoutSessionMutation, session?.customerId]);
|
|
186
|
+
return {
|
|
187
|
+
// Query data
|
|
188
|
+
offers,
|
|
189
|
+
isLoading,
|
|
190
|
+
error,
|
|
191
|
+
// Actions
|
|
192
|
+
refetch: async () => {
|
|
193
|
+
await refetch();
|
|
194
|
+
},
|
|
195
|
+
// Offer management
|
|
196
|
+
createCheckoutSession,
|
|
197
|
+
payOffer,
|
|
198
|
+
transformToCheckout,
|
|
199
|
+
// Helper functions
|
|
200
|
+
getOffer,
|
|
201
|
+
getTotalValue,
|
|
202
|
+
getTotalSavings,
|
|
203
|
+
// Payment functions
|
|
204
|
+
payWithCheckoutSession,
|
|
205
|
+
initCheckoutSession,
|
|
206
|
+
// Checkout session management (similar to postPurchases)
|
|
207
|
+
getCheckoutSessionState: (offerId) => {
|
|
208
|
+
return checkoutSessions[offerId] || null;
|
|
209
|
+
},
|
|
210
|
+
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);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
getAvailableVariants: (offerId, productId) => {
|
|
243
|
+
const sessionState = checkoutSessions[offerId];
|
|
244
|
+
if (!sessionState?.orderSummary?.options?.[productId])
|
|
245
|
+
return [];
|
|
246
|
+
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
247
|
+
variantId: variant.id,
|
|
248
|
+
variantName: variant.name,
|
|
249
|
+
variantSku: variant.sku,
|
|
250
|
+
variantDefault: variant.default,
|
|
251
|
+
variantExternalId: variant.externalVariantId,
|
|
252
|
+
priceId: variant.prices[0]?.id,
|
|
253
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
254
|
+
}));
|
|
255
|
+
},
|
|
256
|
+
selectVariant: async (offerId, productId, variantId) => {
|
|
257
|
+
const sessionState = checkoutSessions[offerId];
|
|
258
|
+
if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
|
|
259
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
260
|
+
}
|
|
261
|
+
const sessionId = sessionState.checkoutSessionId;
|
|
262
|
+
// Set loading state for this specific variant
|
|
263
|
+
setCheckoutSessions(prev => ({
|
|
264
|
+
...prev,
|
|
265
|
+
[offerId]: {
|
|
266
|
+
...prev[offerId],
|
|
267
|
+
loadingVariants: {
|
|
268
|
+
...prev[offerId].loadingVariants,
|
|
269
|
+
[productId]: true,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
try {
|
|
274
|
+
const sessionState = checkoutSessions[offerId];
|
|
275
|
+
if (!sessionState?.orderSummary?.options?.[productId]) {
|
|
276
|
+
throw new Error('No variants available for this product');
|
|
277
|
+
}
|
|
278
|
+
const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
|
|
279
|
+
variantId: variant.id,
|
|
280
|
+
variantName: variant.name,
|
|
281
|
+
variantSku: variant.sku,
|
|
282
|
+
variantDefault: variant.default,
|
|
283
|
+
variantExternalId: variant.externalVariantId,
|
|
284
|
+
priceId: variant.prices[0]?.id,
|
|
285
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
286
|
+
}));
|
|
287
|
+
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
288
|
+
if (!selectedVariant) {
|
|
289
|
+
throw new Error('Selected variant not found');
|
|
290
|
+
}
|
|
291
|
+
// Find the current item to get its quantity
|
|
292
|
+
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
293
|
+
if (!currentItem) {
|
|
294
|
+
throw new Error('Current item not found');
|
|
295
|
+
}
|
|
296
|
+
// Update selected variants state
|
|
297
|
+
setCheckoutSessions(prev => ({
|
|
298
|
+
...prev,
|
|
299
|
+
[offerId]: {
|
|
300
|
+
...prev[offerId],
|
|
301
|
+
selectedVariants: {
|
|
302
|
+
...prev[offerId].selectedVariants,
|
|
303
|
+
[productId]: variantId,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}));
|
|
307
|
+
// Update line items on the server
|
|
308
|
+
await offersResource.updateLineItems(sessionId, [
|
|
309
|
+
{
|
|
310
|
+
variantId: selectedVariant.variantId,
|
|
311
|
+
quantity: currentItem.quantity,
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
314
|
+
// Refetch order summary after successful line item update
|
|
315
|
+
await fetchOrderSummary(offerId, sessionId);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
console.error(`[SDK] Failed to update variant for offer ${offerId}:`, error);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
// Clear loading state for this specific variant
|
|
323
|
+
setCheckoutSessions(prev => ({
|
|
324
|
+
...prev,
|
|
325
|
+
[offerId]: {
|
|
326
|
+
...prev[offerId],
|
|
327
|
+
loadingVariants: {
|
|
328
|
+
...prev[offerId].loadingVariants,
|
|
329
|
+
[productId]: false,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
getOrderSummary: (offerId) => {
|
|
336
|
+
return checkoutSessions[offerId]?.orderSummary ?? null;
|
|
337
|
+
},
|
|
338
|
+
isLoadingVariants: (offerId, productId) => {
|
|
339
|
+
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
340
|
+
},
|
|
341
|
+
isUpdatingOrderSummary: (offerId) => {
|
|
342
|
+
return checkoutSessions[offerId]?.isUpdatingSummary || false;
|
|
343
|
+
},
|
|
344
|
+
confirmPurchase: async (offerId, _options) => {
|
|
345
|
+
const sessionState = checkoutSessions[offerId];
|
|
346
|
+
if (!sessionState?.checkoutSessionId) {
|
|
347
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
348
|
+
}
|
|
349
|
+
// Use the enhanced payWithCheckoutSession with proper metadata
|
|
350
|
+
await offersResource.payWithCheckoutSession(sessionState.checkoutSessionId);
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Bump Hook using TanStack Query
|
|
3
|
+
* Replaces coordinator pattern with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
export interface UseOrderBumpQueryOptions {
|
|
6
|
+
checkoutToken: string | null;
|
|
7
|
+
offerId: string;
|
|
8
|
+
productId?: string;
|
|
9
|
+
checkout?: any;
|
|
10
|
+
}
|
|
11
|
+
export interface UseOrderBumpQueryResult {
|
|
12
|
+
isSelected: boolean;
|
|
13
|
+
isToggling: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
toggle: (selected?: boolean) => Promise<{
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: any;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export declare function useOrderBumpQuery(options: UseOrderBumpQueryOptions): UseOrderBumpQueryResult;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Bump Hook using TanStack Query
|
|
3
|
+
* Replaces coordinator pattern with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
6
|
+
import { useApiMutation, useInvalidateQuery, getGlobalApiClient } from './useApiQuery';
|
|
7
|
+
import { useCheckoutQuery } from './useCheckoutQuery';
|
|
8
|
+
export function useOrderBumpQuery(options) {
|
|
9
|
+
const { checkoutToken, offerId, productId, checkout: providedCheckout } = options;
|
|
10
|
+
const { invalidateCheckout, invalidatePromotions } = useInvalidateQuery();
|
|
11
|
+
const client = getGlobalApiClient();
|
|
12
|
+
// Use checkout query only if no checkout is provided
|
|
13
|
+
const { checkout: loadedCheckout } = useCheckoutQuery({
|
|
14
|
+
checkoutToken: checkoutToken || undefined,
|
|
15
|
+
enabled: !providedCheckout && !!checkoutToken,
|
|
16
|
+
});
|
|
17
|
+
// Use provided checkout or loaded checkout
|
|
18
|
+
const checkout = providedCheckout || loadedCheckout;
|
|
19
|
+
// Function to check if order bump is selected
|
|
20
|
+
const checkOrderBumpSelection = useCallback(() => {
|
|
21
|
+
if (!checkout?.checkoutSession?.sessionLineItems) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const targetProductId = productId || offerId;
|
|
25
|
+
return checkout.checkoutSession.sessionLineItems.some((item) => {
|
|
26
|
+
return item.isOrderBump === true && item.productId === targetProductId;
|
|
27
|
+
});
|
|
28
|
+
}, [checkout?.checkoutSession?.sessionLineItems, offerId, productId]);
|
|
29
|
+
// State management
|
|
30
|
+
const [isSelected, setIsSelected] = useState(() => checkOrderBumpSelection());
|
|
31
|
+
const [error, setError] = useState(null);
|
|
32
|
+
// Update state when checkout session changes
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const isSelectedFromSession = checkOrderBumpSelection();
|
|
35
|
+
setIsSelected(isSelectedFromSession);
|
|
36
|
+
}, [checkOrderBumpSelection]);
|
|
37
|
+
// Order bump toggle mutation
|
|
38
|
+
const toggleMutation = useApiMutation(async ({ selected }) => {
|
|
39
|
+
if (!checkout?.checkoutSession?.id) {
|
|
40
|
+
throw new Error('No checkout session available');
|
|
41
|
+
}
|
|
42
|
+
return client.post(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/toggle-order-bump`, {
|
|
43
|
+
orderBumpOfferId: offerId,
|
|
44
|
+
selected,
|
|
45
|
+
});
|
|
46
|
+
}, {
|
|
47
|
+
onMutate: async ({ selected }) => {
|
|
48
|
+
// Optimistic update
|
|
49
|
+
setIsSelected(selected);
|
|
50
|
+
setError(null);
|
|
51
|
+
},
|
|
52
|
+
onSuccess: () => {
|
|
53
|
+
// Invalidate related queries
|
|
54
|
+
if (checkoutToken) {
|
|
55
|
+
invalidateCheckout(checkoutToken);
|
|
56
|
+
}
|
|
57
|
+
if (checkout?.checkoutSession?.id) {
|
|
58
|
+
invalidatePromotions(checkout.checkoutSession.id);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
onError: (err, { selected }) => {
|
|
62
|
+
// Revert optimistic update on error
|
|
63
|
+
setIsSelected(!selected);
|
|
64
|
+
setError(err instanceof Error ? err : new Error('Failed to toggle order bump'));
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const toggle = useCallback(async (selected) => {
|
|
68
|
+
if (!checkout?.checkoutSession?.id) {
|
|
69
|
+
console.warn('useOrderBumpQuery: No checkout session available yet');
|
|
70
|
+
return { success: false, error: 'Checkout session not ready' };
|
|
71
|
+
}
|
|
72
|
+
const targetState = selected ?? !isSelected;
|
|
73
|
+
try {
|
|
74
|
+
const result = await toggleMutation.mutateAsync({ selected: targetState });
|
|
75
|
+
return { success: true };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const error = err instanceof Error ? err : new Error('Failed to toggle order bump');
|
|
79
|
+
return { success: false, error: error.message };
|
|
80
|
+
}
|
|
81
|
+
}, [checkout?.checkoutSession?.id, isSelected, toggleMutation]);
|
|
82
|
+
return {
|
|
83
|
+
isSelected,
|
|
84
|
+
isToggling: toggleMutation.isPending,
|
|
85
|
+
error,
|
|
86
|
+
toggle,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Hook using TanStack Query
|
|
3
|
+
* Handles order creation and management with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { Order, OrderLineItem } from '../../core/utils/order';
|
|
6
|
+
export interface UseOrderQueryOptions {
|
|
7
|
+
orderId?: string;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseOrderQueryResult {
|
|
11
|
+
order: Order | undefined;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
createOrder: (checkoutSessionId: string) => Promise<{
|
|
15
|
+
success: boolean;
|
|
16
|
+
order?: Order;
|
|
17
|
+
error?: string;
|
|
18
|
+
}>;
|
|
19
|
+
updateOrderStatus: (status: string) => Promise<{
|
|
20
|
+
success: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}>;
|
|
23
|
+
addOrderItems: (items: Omit<OrderLineItem, 'id'>[]) => Promise<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
error?: string;
|
|
26
|
+
}>;
|
|
27
|
+
refresh: () => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function useOrderQuery(options?: UseOrderQueryOptions): UseOrderQueryResult;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Hook using TanStack Query
|
|
3
|
+
* Handles order creation and management with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
7
|
+
import { OrdersResource } from '../../core/resources/orders';
|
|
8
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
9
|
+
export function useOrderQuery(options = {}) {
|
|
10
|
+
const { orderId, enabled = true } = options;
|
|
11
|
+
// Create orders resource client
|
|
12
|
+
const ordersResource = useMemo(() => {
|
|
13
|
+
try {
|
|
14
|
+
return new OrdersResource(getGlobalApiClient());
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw new Error('Failed to initialize orders resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
20
|
+
// Main order query
|
|
21
|
+
const { data: order, isLoading, error, refetch, } = useQuery({
|
|
22
|
+
queryKey: ['order', orderId],
|
|
23
|
+
queryFn: () => ordersResource.getOrder(orderId),
|
|
24
|
+
enabled: enabled && !!orderId,
|
|
25
|
+
staleTime: 60000, // 1 minute
|
|
26
|
+
refetchOnWindowFocus: false,
|
|
27
|
+
});
|
|
28
|
+
// Create order mutation
|
|
29
|
+
const createMutation = useMutation({
|
|
30
|
+
mutationFn: ({ checkoutSessionId }) => {
|
|
31
|
+
return ordersResource.createOrder(checkoutSessionId);
|
|
32
|
+
},
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
// Order created successfully - no specific cache invalidation needed
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
// Update order status mutation
|
|
38
|
+
const updateStatusMutation = useMutation({
|
|
39
|
+
mutationFn: ({ status }) => {
|
|
40
|
+
if (!orderId) {
|
|
41
|
+
throw new Error('No order ID available');
|
|
42
|
+
}
|
|
43
|
+
return ordersResource.updateOrderStatus(orderId, status);
|
|
44
|
+
},
|
|
45
|
+
onSuccess: () => {
|
|
46
|
+
// Refetch order data
|
|
47
|
+
void refetch();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
// Add order items mutation
|
|
51
|
+
const addItemsMutation = useMutation({
|
|
52
|
+
mutationFn: ({ items }) => {
|
|
53
|
+
if (!orderId) {
|
|
54
|
+
throw new Error('No order ID available');
|
|
55
|
+
}
|
|
56
|
+
return ordersResource.addOrderItems(orderId, items);
|
|
57
|
+
},
|
|
58
|
+
onSuccess: () => {
|
|
59
|
+
// Refetch order data
|
|
60
|
+
void refetch();
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
// Query data
|
|
65
|
+
order,
|
|
66
|
+
isLoading,
|
|
67
|
+
error,
|
|
68
|
+
// Actions
|
|
69
|
+
createOrder: async (checkoutSessionId) => {
|
|
70
|
+
try {
|
|
71
|
+
return await createMutation.mutateAsync({ checkoutSessionId });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const error = err instanceof Error ? err : new Error('Failed to create order');
|
|
75
|
+
return { success: false, error: error.message };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
updateOrderStatus: async (status) => {
|
|
79
|
+
try {
|
|
80
|
+
return await updateStatusMutation.mutateAsync({ status });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const error = err instanceof Error ? err : new Error('Failed to update order status');
|
|
84
|
+
return { success: false, error: error.message };
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
addOrderItems: async (items) => {
|
|
88
|
+
try {
|
|
89
|
+
return await addItemsMutation.mutateAsync({ items });
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const error = err instanceof Error ? err : new Error('Failed to add order items');
|
|
93
|
+
return { success: false, error: error.message };
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
refresh: () => void refetch(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface Payment {
|
|
2
|
+
id: string;
|
|
3
|
+
status: string;
|
|
4
|
+
subStatus: string;
|
|
5
|
+
requireAction: 'none' | 'redirect' | 'error';
|
|
6
|
+
requireActionData?: {
|
|
7
|
+
type: 'redirect' | 'threeds_auth' | 'processor_auth' | 'error';
|
|
8
|
+
url?: string;
|
|
9
|
+
processed: boolean;
|
|
10
|
+
processorId?: string;
|
|
11
|
+
metadata?: {
|
|
12
|
+
type: 'redirect';
|
|
13
|
+
redirect?: {
|
|
14
|
+
redirectUrl: string;
|
|
15
|
+
returnUrl: string;
|
|
16
|
+
};
|
|
17
|
+
threedsSession?: {
|
|
18
|
+
externalSessionId: string;
|
|
19
|
+
acsChallengeUrl: string;
|
|
20
|
+
acsTransID: string;
|
|
21
|
+
messageVersion: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
redirectUrl?: string;
|
|
25
|
+
resumeToken?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
errorCode?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface PollingOptions {
|
|
31
|
+
onRequireAction?: (payment: Payment, stop: () => void) => void;
|
|
32
|
+
onSuccess?: (payment: Payment) => void;
|
|
33
|
+
onFailure?: (error: string) => void;
|
|
34
|
+
maxAttempts?: number;
|
|
35
|
+
pollInterval?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface PaymentPollingHook {
|
|
38
|
+
startPolling: (paymentId: string, options?: PollingOptions) => {
|
|
39
|
+
stop: () => void;
|
|
40
|
+
isPolling: () => boolean;
|
|
41
|
+
};
|
|
42
|
+
stopPolling: () => void;
|
|
43
|
+
isPolling: () => boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function usePaymentPolling(): PaymentPollingHook;
|