@tagadapay/plugin-sdk 2.4.39 → 2.5.2

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 +194 -0
  70. package/dist/v2/react/hooks/useCheckoutToken.d.ts +17 -0
  71. package/dist/v2/react/hooks/useCheckoutToken.js +65 -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 +123 -0
  76. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +74 -0
  77. package/dist/v2/react/hooks/useGoogleAutocomplete.js +196 -0
  78. package/dist/v2/react/hooks/useISOData.d.ts +61 -0
  79. package/dist/v2/react/hooks/useISOData.js +175 -0
  80. package/dist/v2/react/hooks/useOffersQuery.d.ts +65 -0
  81. package/dist/v2/react/hooks/useOffersQuery.js +342 -0
  82. package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +20 -0
  83. package/dist/v2/react/hooks/useOrderBumpQuery.js +92 -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 +140 -0
  88. package/dist/v2/react/hooks/usePaymentQuery.d.ts +19 -0
  89. package/dist/v2/react/hooks/usePaymentQuery.js +272 -0
  90. package/dist/v2/react/hooks/usePluginConfig.d.ts +16 -0
  91. package/dist/v2/react/hooks/usePluginConfig.js +35 -0
  92. package/dist/v2/react/hooks/usePostPurchasesQuery.d.ts +63 -0
  93. package/dist/v2/react/hooks/usePostPurchasesQuery.js +343 -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 +150 -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,343 @@
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
+ setCheckoutSessions(prev => ({
64
+ ...prev,
65
+ [offerId]: {
66
+ ...prev[offerId],
67
+ isUpdatingSummary: false,
68
+ }
69
+ }));
70
+ throw error;
71
+ }
72
+ }, [postPurchasesResource]);
73
+ // Main post-purchase offers query
74
+ const { data: offers = [], isLoading, error, refetch, } = useQuery({
75
+ queryKey: ['post-purchase-offers', orderId],
76
+ queryFn: () => postPurchasesResource.getPostPurchaseOffers(orderId),
77
+ enabled: enabled && !!orderId,
78
+ staleTime: 30000, // 30 seconds
79
+ refetchOnWindowFocus: false,
80
+ });
81
+ // Auto-initialize checkout sessions if enabled
82
+ useEffect(() => {
83
+ if (autoInitializeCheckout && offers.length > 0) {
84
+ // Initialize checkout sessions for all offers
85
+ offers.forEach((offer) => {
86
+ try {
87
+ // This is a placeholder - in a full implementation, you'd call initializeOfferCheckout
88
+ }
89
+ catch (_error) {
90
+ // Error handling removed
91
+ }
92
+ });
93
+ }
94
+ }, [autoInitializeCheckout, offers]);
95
+ // Accept offer mutation
96
+ const acceptMutation = useMutation({
97
+ mutationFn: ({ offerId, items }) => {
98
+ if (!orderId) {
99
+ throw new Error('No order ID available');
100
+ }
101
+ return postPurchasesResource.acceptOffer(orderId, offerId, items);
102
+ },
103
+ onSuccess: () => {
104
+ // Refresh offers and potentially checkout data
105
+ void refetch();
106
+ if (orderId) {
107
+ void queryClient.invalidateQueries({ queryKey: ['checkout', orderId] });
108
+ }
109
+ },
110
+ });
111
+ // Decline offer mutation
112
+ const declineMutation = useMutation({
113
+ mutationFn: ({ offerId }) => {
114
+ if (!orderId) {
115
+ throw new Error('No order ID available');
116
+ }
117
+ return postPurchasesResource.declineOffer(orderId, offerId);
118
+ },
119
+ onSuccess: () => {
120
+ // Refresh offers
121
+ void refetch();
122
+ },
123
+ });
124
+ // Preview offer mutation
125
+ const previewMutation = useMutation({
126
+ mutationFn: ({ offerId, items }) => {
127
+ if (!orderId) {
128
+ throw new Error('No order ID available');
129
+ }
130
+ return postPurchasesResource.previewOffer(orderId, offerId, items);
131
+ },
132
+ // No onSuccess needed for preview - it doesn't change state
133
+ });
134
+ return {
135
+ // Query data
136
+ offers,
137
+ isLoading,
138
+ error,
139
+ // Actions
140
+ acceptOffer: async (offerId, items) => {
141
+ try {
142
+ return await acceptMutation.mutateAsync({ offerId, items });
143
+ }
144
+ catch (err) {
145
+ const error = err instanceof Error ? err : new Error('Failed to accept offer');
146
+ return { success: false, error: error.message };
147
+ }
148
+ },
149
+ declineOffer: async (offerId) => {
150
+ try {
151
+ return await declineMutation.mutateAsync({ offerId });
152
+ }
153
+ catch (err) {
154
+ const error = err instanceof Error ? err : new Error('Failed to decline offer');
155
+ return { success: false, error: error.message };
156
+ }
157
+ },
158
+ previewOffer: async (offerId, items) => {
159
+ try {
160
+ return await previewMutation.mutateAsync({ offerId, items });
161
+ }
162
+ catch (err) {
163
+ const error = err instanceof Error ? err : new Error('Failed to preview offer');
164
+ return { success: false, error: error.message };
165
+ }
166
+ },
167
+ refresh: () => void refetch(),
168
+ // V1 compatibility methods
169
+ payWithCheckoutSession: async (checkoutSessionId, orderId) => {
170
+ await postPurchasesResource.payWithCheckoutSession(checkoutSessionId, orderId);
171
+ },
172
+ // Missing V1 methods implementations
173
+ initCheckoutSession: async (offerId, orderId) => {
174
+ // Check if customer ID is available
175
+ if (!session?.customerId) {
176
+ throw new Error('Customer ID is required. Make sure the session is properly initialized.');
177
+ }
178
+ return await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
179
+ },
180
+ initCheckoutSessionWithVariants: async (offerId, orderId, lineItems) => {
181
+ return await postPurchasesResource.initCheckoutSessionWithVariants(offerId, orderId, lineItems);
182
+ },
183
+ getOffer: (offerId) => {
184
+ return offers.find(offer => offer.id === offerId);
185
+ },
186
+ getTotalValue: () => {
187
+ return offers.reduce((total, offer) => {
188
+ return total + offer.summaries.reduce((summaryTotal, summary) => {
189
+ return summaryTotal + summary.totalAmount;
190
+ }, 0);
191
+ }, 0);
192
+ },
193
+ getTotalSavings: () => {
194
+ return offers.reduce((total, offer) => {
195
+ return total + offer.summaries.reduce((summaryTotal, summary) => {
196
+ return summaryTotal + (summary.totalAmount - summary.totalAdjustedAmount);
197
+ }, 0);
198
+ }, 0);
199
+ },
200
+ // Checkout session management - exact same as V1
201
+ getCheckoutSessionState: (offerId) => {
202
+ return checkoutSessions[offerId] || null;
203
+ },
204
+ initializeOfferCheckout: async (offerId) => {
205
+ try {
206
+ // Check if customer ID is available
207
+ if (!session?.customerId) {
208
+ throw new Error('Customer ID is required. Make sure the session is properly initialized.');
209
+ }
210
+ // Initialize checkout session
211
+ const initResult = await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
212
+ if (!initResult.checkoutSessionId) {
213
+ throw new Error('Failed to initialize checkout session');
214
+ }
215
+ const sessionId = initResult.checkoutSessionId;
216
+ // Initialize session state
217
+ setCheckoutSessions(prev => ({
218
+ ...prev,
219
+ [offerId]: {
220
+ checkoutSessionId: sessionId,
221
+ orderSummary: null,
222
+ selectedVariants: {},
223
+ loadingVariants: {},
224
+ isUpdatingSummary: false,
225
+ }
226
+ }));
227
+ // Fetch order summary with variant options
228
+ await fetchOrderSummary(offerId, sessionId);
229
+ }
230
+ catch (_error) {
231
+ throw _error;
232
+ }
233
+ },
234
+ getAvailableVariants: (offerId, productId) => {
235
+ const sessionState = checkoutSessions[offerId];
236
+ if (!sessionState?.orderSummary?.options?.[productId])
237
+ return [];
238
+ return sessionState.orderSummary.options[productId].map((variant) => ({
239
+ variantId: variant.id,
240
+ variantName: variant.name,
241
+ variantSku: variant.sku,
242
+ variantDefault: variant.default,
243
+ variantExternalId: variant.externalVariantId,
244
+ priceId: variant.prices[0]?.id,
245
+ currencyOptions: variant.prices[0]?.currencyOptions,
246
+ }));
247
+ },
248
+ selectVariant: async (offerId, productId, variantId) => {
249
+ const sessionState = checkoutSessions[offerId];
250
+ if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
251
+ throw new Error('Checkout session not initialized for this offer');
252
+ }
253
+ // Set loading state for this specific variant
254
+ setCheckoutSessions(prev => ({
255
+ ...prev,
256
+ [offerId]: {
257
+ ...prev[offerId],
258
+ loadingVariants: {
259
+ ...prev[offerId].loadingVariants,
260
+ [productId]: true,
261
+ }
262
+ }
263
+ }));
264
+ try {
265
+ const sessionState = checkoutSessions[offerId];
266
+ if (!sessionState?.orderSummary?.options?.[productId]) {
267
+ throw new Error('No variants available for this product');
268
+ }
269
+ const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
270
+ variantId: variant.id,
271
+ variantName: variant.name,
272
+ variantSku: variant.sku,
273
+ variantDefault: variant.default,
274
+ variantExternalId: variant.externalVariantId,
275
+ priceId: variant.prices[0]?.id,
276
+ currencyOptions: variant.prices[0]?.currencyOptions,
277
+ }));
278
+ const selectedVariant = availableVariants.find(v => v.variantId === variantId);
279
+ if (!selectedVariant) {
280
+ throw new Error('Selected variant not found');
281
+ }
282
+ // Find the current item to get its quantity
283
+ const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
284
+ if (!currentItem) {
285
+ throw new Error('Current item not found');
286
+ }
287
+ // Update selected variants state
288
+ setCheckoutSessions(prev => ({
289
+ ...prev,
290
+ [offerId]: {
291
+ ...prev[offerId],
292
+ selectedVariants: {
293
+ ...prev[offerId].selectedVariants,
294
+ [productId]: variantId,
295
+ }
296
+ }
297
+ }));
298
+ // Update line items on the server
299
+ await postPurchasesResource.updateLineItems(sessionState.checkoutSessionId, [
300
+ {
301
+ variantId: selectedVariant.variantId,
302
+ quantity: currentItem.quantity,
303
+ },
304
+ ]);
305
+ // Refetch order summary after successful line item update
306
+ await fetchOrderSummary(offerId, sessionState.checkoutSessionId);
307
+ }
308
+ catch (_error) {
309
+ throw _error;
310
+ }
311
+ finally {
312
+ // Clear loading state for this specific variant
313
+ setCheckoutSessions(prev => ({
314
+ ...prev,
315
+ [offerId]: {
316
+ ...prev[offerId],
317
+ loadingVariants: {
318
+ ...prev[offerId].loadingVariants,
319
+ [productId]: false,
320
+ }
321
+ }
322
+ }));
323
+ }
324
+ },
325
+ getOrderSummary: (offerId) => {
326
+ return checkoutSessions[offerId]?.orderSummary ?? null;
327
+ },
328
+ isLoadingVariants: (offerId, productId) => {
329
+ return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
330
+ },
331
+ isUpdatingOrderSummary: (offerId) => {
332
+ return checkoutSessions[offerId]?.isUpdatingSummary || false;
333
+ },
334
+ confirmPurchase: async (offerId, _options) => {
335
+ const sessionState = checkoutSessions[offerId];
336
+ if (!sessionState?.checkoutSessionId) {
337
+ throw new Error('Checkout session not initialized for this offer');
338
+ }
339
+ // Use the enhanced payWithCheckoutSession with proper metadata
340
+ await postPurchasesResource.payWithCheckoutSession(sessionState.checkoutSessionId, orderId);
341
+ },
342
+ };
343
+ }
@@ -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 {};