@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,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post Purchases Hook using TanStack Query
|
|
3
|
+
* Handles post-purchase offers with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo, useEffect, useState, useCallback } from 'react';
|
|
6
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { PostPurchasesResource } from '../../core/resources/postPurchases';
|
|
8
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
10
|
+
export function usePostPurchasesQuery(options) {
|
|
11
|
+
const { orderId, enabled = true, autoInitializeCheckout = false } = options;
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
const { session } = useTagadaContext();
|
|
14
|
+
// State for checkout sessions per offer
|
|
15
|
+
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
16
|
+
// Create post purchases resource client
|
|
17
|
+
const postPurchasesResource = useMemo(() => {
|
|
18
|
+
try {
|
|
19
|
+
return new PostPurchasesResource(getGlobalApiClient());
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error('Failed to initialize post purchases resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
// Helper function for fetching order summary - exact same as V1
|
|
26
|
+
const fetchOrderSummary = useCallback(async (offerId, sessionId) => {
|
|
27
|
+
try {
|
|
28
|
+
// Set updating state
|
|
29
|
+
setCheckoutSessions(prev => ({
|
|
30
|
+
...prev,
|
|
31
|
+
[offerId]: {
|
|
32
|
+
...prev[offerId],
|
|
33
|
+
isUpdatingSummary: true,
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
const summaryResult = await postPurchasesResource.getOrderSummary(sessionId, true);
|
|
37
|
+
if (summaryResult) {
|
|
38
|
+
// Sort items by productId to ensure consistent order
|
|
39
|
+
const sortedItems = [...summaryResult.items].sort((a, b) => a.productId.localeCompare(b.productId));
|
|
40
|
+
const orderSummary = {
|
|
41
|
+
...summaryResult,
|
|
42
|
+
items: sortedItems,
|
|
43
|
+
};
|
|
44
|
+
// Initialize selected variants based on the summary
|
|
45
|
+
const initialVariants = {};
|
|
46
|
+
sortedItems.forEach((item) => {
|
|
47
|
+
if (item.productId && item.variantId) {
|
|
48
|
+
initialVariants[item.productId] = item.variantId;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
setCheckoutSessions(prev => ({
|
|
52
|
+
...prev,
|
|
53
|
+
[offerId]: {
|
|
54
|
+
...prev[offerId],
|
|
55
|
+
orderSummary,
|
|
56
|
+
selectedVariants: initialVariants,
|
|
57
|
+
isUpdatingSummary: false,
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error(`[SDK] Failed to fetch order summary for offer ${offerId}:`, error);
|
|
64
|
+
setCheckoutSessions(prev => ({
|
|
65
|
+
...prev,
|
|
66
|
+
[offerId]: {
|
|
67
|
+
...prev[offerId],
|
|
68
|
+
isUpdatingSummary: false,
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}, [postPurchasesResource]);
|
|
74
|
+
// Main post-purchase offers query
|
|
75
|
+
const { data: offers = [], isLoading, error, refetch, } = useQuery({
|
|
76
|
+
queryKey: ['post-purchase-offers', orderId],
|
|
77
|
+
queryFn: () => postPurchasesResource.getPostPurchaseOffers(orderId),
|
|
78
|
+
enabled: enabled && !!orderId,
|
|
79
|
+
staleTime: 30000, // 30 seconds
|
|
80
|
+
refetchOnWindowFocus: false,
|
|
81
|
+
});
|
|
82
|
+
// Auto-initialize checkout sessions if enabled
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (autoInitializeCheckout && offers.length > 0) {
|
|
85
|
+
// Initialize checkout sessions for all offers
|
|
86
|
+
offers.forEach((offer) => {
|
|
87
|
+
try {
|
|
88
|
+
// This is a placeholder - in a full implementation, you'd call initializeOfferCheckout
|
|
89
|
+
console.log(`Auto-initializing checkout for offer: ${offer.id}`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`Failed to auto-initialize checkout for offer ${offer.id}:`, error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}, [autoInitializeCheckout, offers]);
|
|
97
|
+
// Accept offer mutation
|
|
98
|
+
const acceptMutation = useMutation({
|
|
99
|
+
mutationFn: ({ offerId, items }) => {
|
|
100
|
+
if (!orderId) {
|
|
101
|
+
throw new Error('No order ID available');
|
|
102
|
+
}
|
|
103
|
+
return postPurchasesResource.acceptOffer(orderId, offerId, items);
|
|
104
|
+
},
|
|
105
|
+
onSuccess: () => {
|
|
106
|
+
// Refresh offers and potentially checkout data
|
|
107
|
+
void refetch();
|
|
108
|
+
if (orderId) {
|
|
109
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', orderId] });
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
// Decline offer mutation
|
|
114
|
+
const declineMutation = useMutation({
|
|
115
|
+
mutationFn: ({ offerId }) => {
|
|
116
|
+
if (!orderId) {
|
|
117
|
+
throw new Error('No order ID available');
|
|
118
|
+
}
|
|
119
|
+
return postPurchasesResource.declineOffer(orderId, offerId);
|
|
120
|
+
},
|
|
121
|
+
onSuccess: () => {
|
|
122
|
+
// Refresh offers
|
|
123
|
+
void refetch();
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
// Preview offer mutation
|
|
127
|
+
const previewMutation = useMutation({
|
|
128
|
+
mutationFn: ({ offerId, items }) => {
|
|
129
|
+
if (!orderId) {
|
|
130
|
+
throw new Error('No order ID available');
|
|
131
|
+
}
|
|
132
|
+
return postPurchasesResource.previewOffer(orderId, offerId, items);
|
|
133
|
+
},
|
|
134
|
+
// No onSuccess needed for preview - it doesn't change state
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
// Query data
|
|
138
|
+
offers,
|
|
139
|
+
isLoading,
|
|
140
|
+
error,
|
|
141
|
+
// Actions
|
|
142
|
+
acceptOffer: async (offerId, items) => {
|
|
143
|
+
try {
|
|
144
|
+
return await acceptMutation.mutateAsync({ offerId, items });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const error = err instanceof Error ? err : new Error('Failed to accept offer');
|
|
148
|
+
return { success: false, error: error.message };
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
declineOffer: async (offerId) => {
|
|
152
|
+
try {
|
|
153
|
+
return await declineMutation.mutateAsync({ offerId });
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
const error = err instanceof Error ? err : new Error('Failed to decline offer');
|
|
157
|
+
return { success: false, error: error.message };
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
previewOffer: async (offerId, items) => {
|
|
161
|
+
try {
|
|
162
|
+
return await previewMutation.mutateAsync({ offerId, items });
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
const error = err instanceof Error ? err : new Error('Failed to preview offer');
|
|
166
|
+
return { success: false, error: error.message };
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
refresh: () => void refetch(),
|
|
170
|
+
// V1 compatibility methods
|
|
171
|
+
payWithCheckoutSession: async (checkoutSessionId, orderId) => {
|
|
172
|
+
try {
|
|
173
|
+
await postPurchasesResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error('Failed to pay with checkout session:', err);
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
// Missing V1 methods implementations
|
|
181
|
+
initCheckoutSession: async (offerId, orderId) => {
|
|
182
|
+
try {
|
|
183
|
+
// Check if customer ID is available
|
|
184
|
+
if (!session?.customerId) {
|
|
185
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
186
|
+
}
|
|
187
|
+
return await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.error('Failed to initialize checkout session:', err);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
initCheckoutSessionWithVariants: async (offerId, orderId, lineItems) => {
|
|
195
|
+
try {
|
|
196
|
+
return await postPurchasesResource.initCheckoutSessionWithVariants(offerId, orderId, lineItems);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error('Failed to initialize checkout session with variants:', err);
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
getOffer: (offerId) => {
|
|
204
|
+
return offers.find(offer => offer.id === offerId);
|
|
205
|
+
},
|
|
206
|
+
getTotalValue: () => {
|
|
207
|
+
return offers.reduce((total, offer) => {
|
|
208
|
+
return total + offer.summaries.reduce((summaryTotal, summary) => {
|
|
209
|
+
return summaryTotal + summary.totalAmount;
|
|
210
|
+
}, 0);
|
|
211
|
+
}, 0);
|
|
212
|
+
},
|
|
213
|
+
getTotalSavings: () => {
|
|
214
|
+
return offers.reduce((total, offer) => {
|
|
215
|
+
return total + offer.summaries.reduce((summaryTotal, summary) => {
|
|
216
|
+
return summaryTotal + (summary.totalAmount - summary.totalAdjustedAmount);
|
|
217
|
+
}, 0);
|
|
218
|
+
}, 0);
|
|
219
|
+
},
|
|
220
|
+
// Checkout session management - exact same as V1
|
|
221
|
+
getCheckoutSessionState: (offerId) => {
|
|
222
|
+
return checkoutSessions[offerId] || null;
|
|
223
|
+
},
|
|
224
|
+
initializeOfferCheckout: async (offerId) => {
|
|
225
|
+
try {
|
|
226
|
+
// Check if customer ID is available
|
|
227
|
+
if (!session?.customerId) {
|
|
228
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
229
|
+
}
|
|
230
|
+
// Initialize checkout session
|
|
231
|
+
const initResult = await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
232
|
+
if (!initResult.checkoutSessionId) {
|
|
233
|
+
throw new Error('Failed to initialize checkout session');
|
|
234
|
+
}
|
|
235
|
+
const sessionId = initResult.checkoutSessionId;
|
|
236
|
+
// Initialize session state
|
|
237
|
+
setCheckoutSessions(prev => ({
|
|
238
|
+
...prev,
|
|
239
|
+
[offerId]: {
|
|
240
|
+
checkoutSessionId: sessionId,
|
|
241
|
+
orderSummary: null,
|
|
242
|
+
selectedVariants: {},
|
|
243
|
+
loadingVariants: {},
|
|
244
|
+
isUpdatingSummary: false,
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
// Fetch order summary with variant options
|
|
248
|
+
await fetchOrderSummary(offerId, sessionId);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
getAvailableVariants: (offerId, productId) => {
|
|
256
|
+
const sessionState = checkoutSessions[offerId];
|
|
257
|
+
if (!sessionState?.orderSummary?.options?.[productId])
|
|
258
|
+
return [];
|
|
259
|
+
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
260
|
+
variantId: variant.id,
|
|
261
|
+
variantName: variant.name,
|
|
262
|
+
variantSku: variant.sku,
|
|
263
|
+
variantDefault: variant.default,
|
|
264
|
+
variantExternalId: variant.externalVariantId,
|
|
265
|
+
priceId: variant.prices[0]?.id,
|
|
266
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
267
|
+
}));
|
|
268
|
+
},
|
|
269
|
+
selectVariant: async (offerId, productId, variantId) => {
|
|
270
|
+
const sessionState = checkoutSessions[offerId];
|
|
271
|
+
if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
|
|
272
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
273
|
+
}
|
|
274
|
+
// Set loading state for this specific variant
|
|
275
|
+
setCheckoutSessions(prev => ({
|
|
276
|
+
...prev,
|
|
277
|
+
[offerId]: {
|
|
278
|
+
...prev[offerId],
|
|
279
|
+
loadingVariants: {
|
|
280
|
+
...prev[offerId].loadingVariants,
|
|
281
|
+
[productId]: true,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}));
|
|
285
|
+
try {
|
|
286
|
+
const sessionState = checkoutSessions[offerId];
|
|
287
|
+
if (!sessionState?.orderSummary?.options?.[productId]) {
|
|
288
|
+
throw new Error('No variants available for this product');
|
|
289
|
+
}
|
|
290
|
+
const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
|
|
291
|
+
variantId: variant.id,
|
|
292
|
+
variantName: variant.name,
|
|
293
|
+
variantSku: variant.sku,
|
|
294
|
+
variantDefault: variant.default,
|
|
295
|
+
variantExternalId: variant.externalVariantId,
|
|
296
|
+
priceId: variant.prices[0]?.id,
|
|
297
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
298
|
+
}));
|
|
299
|
+
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
300
|
+
if (!selectedVariant) {
|
|
301
|
+
throw new Error('Selected variant not found');
|
|
302
|
+
}
|
|
303
|
+
// Find the current item to get its quantity
|
|
304
|
+
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
305
|
+
if (!currentItem) {
|
|
306
|
+
throw new Error('Current item not found');
|
|
307
|
+
}
|
|
308
|
+
// Update selected variants state
|
|
309
|
+
setCheckoutSessions(prev => ({
|
|
310
|
+
...prev,
|
|
311
|
+
[offerId]: {
|
|
312
|
+
...prev[offerId],
|
|
313
|
+
selectedVariants: {
|
|
314
|
+
...prev[offerId].selectedVariants,
|
|
315
|
+
[productId]: variantId,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}));
|
|
319
|
+
// Update line items on the server
|
|
320
|
+
await postPurchasesResource.updateLineItems(sessionState.checkoutSessionId, [
|
|
321
|
+
{
|
|
322
|
+
variantId: selectedVariant.variantId,
|
|
323
|
+
quantity: currentItem.quantity,
|
|
324
|
+
},
|
|
325
|
+
]);
|
|
326
|
+
// Refetch order summary after successful line item update
|
|
327
|
+
await fetchOrderSummary(offerId, sessionState.checkoutSessionId);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error(`[SDK] Failed to update variant for offer ${offerId}:`, error);
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
// Clear loading state for this specific variant
|
|
335
|
+
setCheckoutSessions(prev => ({
|
|
336
|
+
...prev,
|
|
337
|
+
[offerId]: {
|
|
338
|
+
...prev[offerId],
|
|
339
|
+
loadingVariants: {
|
|
340
|
+
...prev[offerId].loadingVariants,
|
|
341
|
+
[productId]: false,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
getOrderSummary: (offerId) => {
|
|
348
|
+
return checkoutSessions[offerId]?.orderSummary ?? null;
|
|
349
|
+
},
|
|
350
|
+
isLoadingVariants: (offerId, productId) => {
|
|
351
|
+
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
352
|
+
},
|
|
353
|
+
isUpdatingOrderSummary: (offerId) => {
|
|
354
|
+
return checkoutSessions[offerId]?.isUpdatingSummary || false;
|
|
355
|
+
},
|
|
356
|
+
confirmPurchase: async (offerId, _options) => {
|
|
357
|
+
const sessionState = checkoutSessions[offerId];
|
|
358
|
+
if (!sessionState?.checkoutSessionId) {
|
|
359
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
360
|
+
}
|
|
361
|
+
// Use the enhanced payWithCheckoutSession with proper metadata
|
|
362
|
+
await postPurchasesResource.payWithCheckoutSession(sessionState.checkoutSessionId, orderId);
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products Hook using TanStack Query
|
|
3
|
+
* Replaces manual fetching with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { Product, ProductVariant } from '../../core/resources/products';
|
|
6
|
+
export interface UseProductsQueryOptions {
|
|
7
|
+
productIds?: string[];
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
includeVariants?: boolean;
|
|
10
|
+
includePrices?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UseProductsQueryResult {
|
|
13
|
+
products: Product[];
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
error: Error | null;
|
|
16
|
+
refetch: () => Promise<void>;
|
|
17
|
+
getProduct: (productId: string) => Product | undefined;
|
|
18
|
+
getVariant: (variantId: string) => {
|
|
19
|
+
product: Product;
|
|
20
|
+
variant: ProductVariant;
|
|
21
|
+
} | undefined;
|
|
22
|
+
getAllVariants: () => {
|
|
23
|
+
product: Product;
|
|
24
|
+
variant: ProductVariant;
|
|
25
|
+
}[];
|
|
26
|
+
filterVariants: (predicate: (variant: ProductVariant, product: Product) => boolean) => {
|
|
27
|
+
product: Product;
|
|
28
|
+
variant: ProductVariant;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
export declare function useProductsQuery(options?: UseProductsQueryOptions): UseProductsQueryResult;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products Hook using TanStack Query
|
|
3
|
+
* Replaces manual fetching with automatic caching
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo, useCallback } from 'react';
|
|
6
|
+
import { useQuery } from '@tanstack/react-query';
|
|
7
|
+
import { ProductsResource } from '../../core/resources/products';
|
|
8
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
10
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
11
|
+
export function useProductsQuery(options = {}) {
|
|
12
|
+
const { productIds = [], enabled = true, includeVariants = true, includePrices = true } = options;
|
|
13
|
+
const { storeId } = usePluginConfig();
|
|
14
|
+
const { isSessionInitialized } = useTagadaContext();
|
|
15
|
+
// Create products resource client
|
|
16
|
+
const productsResource = useMemo(() => {
|
|
17
|
+
try {
|
|
18
|
+
return new ProductsResource(getGlobalApiClient());
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error('Failed to initialize products resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
22
|
+
}
|
|
23
|
+
}, []);
|
|
24
|
+
// Create query key based on options
|
|
25
|
+
const queryKey = useMemo(() => ['products', { storeId, productIds, includeVariants, includePrices }], [storeId, productIds, includeVariants, includePrices]);
|
|
26
|
+
// Use TanStack Query for fetching products
|
|
27
|
+
const { data: products = [], isLoading, error, refetch } = useQuery({
|
|
28
|
+
queryKey,
|
|
29
|
+
enabled: enabled && !!storeId && isSessionInitialized,
|
|
30
|
+
queryFn: async () => {
|
|
31
|
+
if (!storeId) {
|
|
32
|
+
throw new Error('Store ID not found. Make sure the TagadaProvider is properly configured.');
|
|
33
|
+
}
|
|
34
|
+
const options = {
|
|
35
|
+
storeId,
|
|
36
|
+
includeVariants,
|
|
37
|
+
includePrices,
|
|
38
|
+
};
|
|
39
|
+
if (productIds.length > 0) {
|
|
40
|
+
// Fetch specific products
|
|
41
|
+
return await productsResource.getProductsByIds(productIds, options);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Fetch all products for the store
|
|
45
|
+
return await productsResource.getProducts(options);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
49
|
+
});
|
|
50
|
+
// Helper functions (matching old useProducts exactly)
|
|
51
|
+
const getProduct = useCallback((productId) => {
|
|
52
|
+
return products.find((product) => product.id === productId);
|
|
53
|
+
}, [products]);
|
|
54
|
+
const getVariant = useCallback((variantId) => {
|
|
55
|
+
for (const product of products) {
|
|
56
|
+
const variant = product.variants?.find((v) => v.id === variantId);
|
|
57
|
+
if (variant) {
|
|
58
|
+
return { product, variant };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}, [products]);
|
|
63
|
+
const getAllVariants = useCallback(() => {
|
|
64
|
+
const allVariants = [];
|
|
65
|
+
for (const product of products) {
|
|
66
|
+
if (product.variants) {
|
|
67
|
+
for (const variant of product.variants) {
|
|
68
|
+
allVariants.push({ product, variant });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return allVariants;
|
|
73
|
+
}, [products]);
|
|
74
|
+
const filterVariants = useCallback((predicate) => {
|
|
75
|
+
const filtered = [];
|
|
76
|
+
for (const product of products) {
|
|
77
|
+
if (product.variants) {
|
|
78
|
+
for (const variant of product.variants) {
|
|
79
|
+
if (predicate(variant, product)) {
|
|
80
|
+
filtered.push({ product, variant });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return filtered;
|
|
86
|
+
}, [products]);
|
|
87
|
+
return {
|
|
88
|
+
// Query data
|
|
89
|
+
products,
|
|
90
|
+
isLoading,
|
|
91
|
+
error,
|
|
92
|
+
// Actions
|
|
93
|
+
refetch: async () => {
|
|
94
|
+
await refetch();
|
|
95
|
+
},
|
|
96
|
+
// Helper functions
|
|
97
|
+
getProduct,
|
|
98
|
+
getVariant,
|
|
99
|
+
getAllVariants,
|
|
100
|
+
filterVariants,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promotions Hook using TanStack Query
|
|
3
|
+
* Replaces manual refresh with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { Promotion } from '../../core/resources/checkout';
|
|
6
|
+
export interface UsePromotionsQueryOptions {
|
|
7
|
+
checkoutSessionId?: string;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UsePromotionsQueryResult {
|
|
11
|
+
appliedPromotions: Promotion[];
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
isApplying: boolean;
|
|
15
|
+
isRemoving: boolean;
|
|
16
|
+
applyPromotionCode: (code: string) => Promise<{
|
|
17
|
+
success: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
promotion?: Promotion;
|
|
20
|
+
}>;
|
|
21
|
+
removePromotionCode: (promotionId: string) => Promise<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}>;
|
|
25
|
+
refresh: () => Promise<void>;
|
|
26
|
+
clearError: () => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function usePromotionsQuery(options?: UsePromotionsQueryOptions): UsePromotionsQueryResult;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promotions Hook using TanStack Query
|
|
3
|
+
* Replaces manual refresh with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { PromotionsResource } from '../../core/resources/promotions';
|
|
8
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
9
|
+
export function usePromotionsQuery(options = {}) {
|
|
10
|
+
const { checkoutSessionId, enabled = true } = options;
|
|
11
|
+
const queryClient = useQueryClient();
|
|
12
|
+
const client = getGlobalApiClient();
|
|
13
|
+
// Create resource client
|
|
14
|
+
const promotionsResource = useMemo(() => {
|
|
15
|
+
try {
|
|
16
|
+
return new PromotionsResource(client);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error('Failed to initialize promotions resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
20
|
+
}
|
|
21
|
+
}, [client]);
|
|
22
|
+
// Main promotions query
|
|
23
|
+
const { data: appliedPromotions = [], isLoading, error, refetch, } = useQuery({
|
|
24
|
+
queryKey: ['promotions', checkoutSessionId],
|
|
25
|
+
queryFn: () => promotionsResource.getAppliedPromotions(checkoutSessionId),
|
|
26
|
+
enabled: enabled && !!checkoutSessionId,
|
|
27
|
+
staleTime: 30000, // 30 seconds
|
|
28
|
+
refetchOnWindowFocus: false,
|
|
29
|
+
});
|
|
30
|
+
// Apply promotion mutation
|
|
31
|
+
const applyMutation = useMutation({
|
|
32
|
+
mutationFn: ({ code }) => {
|
|
33
|
+
if (!checkoutSessionId) {
|
|
34
|
+
throw new Error('No checkout session available');
|
|
35
|
+
}
|
|
36
|
+
return promotionsResource.applyPromotionCode(checkoutSessionId, code);
|
|
37
|
+
},
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
// Invalidate related queries
|
|
40
|
+
if (checkoutSessionId) {
|
|
41
|
+
void queryClient.invalidateQueries({ queryKey: ['promotions', checkoutSessionId] });
|
|
42
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutSessionId] });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
// Remove promotion mutation
|
|
47
|
+
const removeMutation = useMutation({
|
|
48
|
+
mutationFn: ({ promotionId }) => {
|
|
49
|
+
if (!checkoutSessionId) {
|
|
50
|
+
throw new Error('No checkout session available');
|
|
51
|
+
}
|
|
52
|
+
return promotionsResource.removePromotionCode(checkoutSessionId, promotionId);
|
|
53
|
+
},
|
|
54
|
+
onSuccess: () => {
|
|
55
|
+
// Invalidate related queries
|
|
56
|
+
if (checkoutSessionId) {
|
|
57
|
+
void queryClient.invalidateQueries({ queryKey: ['promotions', checkoutSessionId] });
|
|
58
|
+
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutSessionId] });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
// Query data
|
|
64
|
+
appliedPromotions,
|
|
65
|
+
isLoading,
|
|
66
|
+
error,
|
|
67
|
+
// Loading states for mutations
|
|
68
|
+
isApplying: applyMutation.isPending,
|
|
69
|
+
isRemoving: removeMutation.isPending,
|
|
70
|
+
// Actions
|
|
71
|
+
applyPromotionCode: async (code) => {
|
|
72
|
+
try {
|
|
73
|
+
return await applyMutation.mutateAsync({ code });
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const error = err instanceof Error ? err : new Error('Failed to apply promotion code');
|
|
77
|
+
return { success: false, error: error.message };
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
removePromotionCode: async (promotionId) => {
|
|
81
|
+
try {
|
|
82
|
+
return await removeMutation.mutateAsync({ promotionId });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const error = err instanceof Error ? err : new Error('Failed to remove promotion code');
|
|
86
|
+
return { success: false, error: error.message };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
refresh: async () => {
|
|
90
|
+
await refetch();
|
|
91
|
+
},
|
|
92
|
+
clearError: () => {
|
|
93
|
+
// Clear query error by refetching
|
|
94
|
+
void refetch();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ThreedsProvider = 'basis_theory';
|
|
2
|
+
export interface PaymentInstrument {
|
|
3
|
+
id: string;
|
|
4
|
+
token: string | null;
|
|
5
|
+
type: string;
|
|
6
|
+
card: {
|
|
7
|
+
maskedCardNumber?: string;
|
|
8
|
+
expirationMonth?: number;
|
|
9
|
+
expirationYear?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface ThreedsSession {
|
|
13
|
+
id: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ThreedsChallenge {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
acsChallengeUrl: string;
|
|
20
|
+
acsTransactionId: string;
|
|
21
|
+
threeDSVersion: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ThreedsOptions {
|
|
24
|
+
provider?: ThreedsProvider;
|
|
25
|
+
}
|
|
26
|
+
export interface ThreedsHook {
|
|
27
|
+
createSession: (paymentInstrument: PaymentInstrument, options?: ThreedsOptions) => Promise<ThreedsSession>;
|
|
28
|
+
startChallenge: (challengeData: ThreedsChallenge, options?: ThreedsOptions) => Promise<any>;
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
}
|
|
32
|
+
interface UseThreedsOptions {
|
|
33
|
+
defaultProvider?: ThreedsProvider;
|
|
34
|
+
}
|
|
35
|
+
export declare function useThreeds(options?: UseThreedsOptions): ThreedsHook;
|
|
36
|
+
export {};
|