@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.
Files changed (106) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2 -0
  3. package/dist/react/hooks/useCheckout.js +19 -2
  4. package/dist/react/hooks/useCheckoutSession.d.ts +19 -0
  5. package/dist/react/hooks/useCheckoutSession.js +108 -0
  6. package/dist/react/hooks/useCheckoutToken.d.ts +17 -0
  7. package/dist/react/hooks/useCheckoutToken.js +80 -0
  8. package/dist/react/hooks/useOrderBump.js +92 -13
  9. package/dist/react/hooks/useOrderBumpV2.d.ts +17 -0
  10. package/dist/react/hooks/useOrderBumpV2.js +95 -0
  11. package/dist/react/hooks/useOrderBumpV3.d.ts +23 -0
  12. package/dist/react/hooks/useOrderBumpV3.js +109 -0
  13. package/dist/react/hooks/usePostPurchases.js +11 -5
  14. package/dist/react/index.d.ts +8 -0
  15. package/dist/react/index.js +4 -0
  16. package/dist/react/services/apiService.d.ts +1 -0
  17. package/dist/react/services/apiService.js +3 -0
  18. package/dist/v2/core/googleAutocomplete.d.ts +65 -0
  19. package/dist/v2/core/googleAutocomplete.js +94 -0
  20. package/dist/v2/core/index.d.ts +8 -0
  21. package/dist/v2/core/index.js +11 -0
  22. package/dist/v2/core/isoData.d.ts +50 -0
  23. package/dist/v2/core/isoData.js +103 -0
  24. package/dist/v2/core/resources/apiClient.d.ts +25 -0
  25. package/dist/v2/core/resources/apiClient.js +95 -0
  26. package/dist/v2/core/resources/checkout.d.ts +189 -0
  27. package/dist/v2/core/resources/checkout.js +119 -0
  28. package/dist/v2/core/resources/index.d.ts +13 -0
  29. package/dist/v2/core/resources/index.js +13 -0
  30. package/dist/v2/core/resources/offers.d.ts +98 -0
  31. package/dist/v2/core/resources/offers.js +115 -0
  32. package/dist/v2/core/resources/orders.d.ts +40 -0
  33. package/dist/v2/core/resources/orders.js +59 -0
  34. package/dist/v2/core/resources/payments.d.ts +140 -0
  35. package/dist/v2/core/resources/payments.js +126 -0
  36. package/dist/v2/core/resources/postPurchases.d.ts +182 -0
  37. package/dist/v2/core/resources/postPurchases.js +116 -0
  38. package/dist/v2/core/resources/products.d.ts +29 -0
  39. package/dist/v2/core/resources/products.js +49 -0
  40. package/dist/v2/core/resources/promotions.d.ts +45 -0
  41. package/dist/v2/core/resources/promotions.js +87 -0
  42. package/dist/v2/core/resources/threeds.d.ts +23 -0
  43. package/dist/v2/core/resources/threeds.js +15 -0
  44. package/dist/v2/core/utils/checkout.d.ts +24 -0
  45. package/dist/v2/core/utils/checkout.js +30 -0
  46. package/dist/v2/core/utils/currency.d.ts +28 -0
  47. package/dist/v2/core/utils/currency.js +272 -0
  48. package/dist/v2/core/utils/index.d.ts +12 -0
  49. package/dist/v2/core/utils/index.js +12 -0
  50. package/dist/v2/core/utils/order.d.ts +159 -0
  51. package/dist/v2/core/utils/order.js +42 -0
  52. package/dist/v2/core/utils/orderBump.d.ts +40 -0
  53. package/dist/v2/core/utils/orderBump.js +47 -0
  54. package/dist/v2/core/utils/pluginConfig.d.ts +43 -0
  55. package/dist/v2/core/utils/pluginConfig.js +155 -0
  56. package/dist/v2/core/utils/postPurchases.d.ts +32 -0
  57. package/dist/v2/core/utils/postPurchases.js +42 -0
  58. package/dist/v2/core/utils/products.d.ts +58 -0
  59. package/dist/v2/core/utils/products.js +64 -0
  60. package/dist/v2/core/utils/promotions.d.ts +24 -0
  61. package/dist/v2/core/utils/promotions.js +30 -0
  62. package/dist/v2/index.d.ts +19 -0
  63. package/dist/v2/index.js +15 -0
  64. package/dist/v2/react/components/DebugDrawer.d.ts +7 -0
  65. package/dist/v2/react/components/DebugDrawer.js +383 -0
  66. package/dist/v2/react/hooks/useApiQuery.d.ts +28 -0
  67. package/dist/v2/react/hooks/useApiQuery.js +84 -0
  68. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +39 -0
  69. package/dist/v2/react/hooks/useCheckoutQuery.js +208 -0
  70. package/dist/v2/react/hooks/useCheckoutToken.d.ts +17 -0
  71. package/dist/v2/react/hooks/useCheckoutToken.js +80 -0
  72. package/dist/v2/react/hooks/useCurrency.d.ts +9 -0
  73. package/dist/v2/react/hooks/useCurrency.js +21 -0
  74. package/dist/v2/react/hooks/useGeoLocation.d.ts +138 -0
  75. package/dist/v2/react/hooks/useGeoLocation.js +126 -0
  76. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +74 -0
  77. package/dist/v2/react/hooks/useGoogleAutocomplete.js +207 -0
  78. package/dist/v2/react/hooks/useISOData.d.ts +61 -0
  79. package/dist/v2/react/hooks/useISOData.js +176 -0
  80. package/dist/v2/react/hooks/useOffersQuery.d.ts +65 -0
  81. package/dist/v2/react/hooks/useOffersQuery.js +353 -0
  82. package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +20 -0
  83. package/dist/v2/react/hooks/useOrderBumpQuery.js +88 -0
  84. package/dist/v2/react/hooks/useOrderQuery.d.ts +29 -0
  85. package/dist/v2/react/hooks/useOrderQuery.js +98 -0
  86. package/dist/v2/react/hooks/usePaymentPolling.d.ts +45 -0
  87. package/dist/v2/react/hooks/usePaymentPolling.js +153 -0
  88. package/dist/v2/react/hooks/usePaymentQuery.d.ts +19 -0
  89. package/dist/v2/react/hooks/usePaymentQuery.js +283 -0
  90. package/dist/v2/react/hooks/usePluginConfig.d.ts +16 -0
  91. package/dist/v2/react/hooks/usePluginConfig.js +36 -0
  92. package/dist/v2/react/hooks/usePostPurchasesQuery.d.ts +63 -0
  93. package/dist/v2/react/hooks/usePostPurchasesQuery.js +365 -0
  94. package/dist/v2/react/hooks/useProductsQuery.d.ts +31 -0
  95. package/dist/v2/react/hooks/useProductsQuery.js +102 -0
  96. package/dist/v2/react/hooks/usePromotionsQuery.d.ts +28 -0
  97. package/dist/v2/react/hooks/usePromotionsQuery.js +97 -0
  98. package/dist/v2/react/hooks/useThreeds.d.ts +36 -0
  99. package/dist/v2/react/hooks/useThreeds.js +166 -0
  100. package/dist/v2/react/hooks/useThreedsModal.d.ts +13 -0
  101. package/dist/v2/react/hooks/useThreedsModal.js +343 -0
  102. package/dist/v2/react/index.d.ts +38 -0
  103. package/dist/v2/react/index.js +27 -0
  104. package/dist/v2/react/providers/TagadaProvider.d.ts +63 -0
  105. package/dist/v2/react/providers/TagadaProvider.js +680 -0
  106. 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 {};