@tagadapay/plugin-sdk 2.8.8 → 2.8.9
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/react/config/environment.d.ts +1 -22
- package/dist/react/config/environment.js +1 -132
- package/dist/react/utils/deviceInfo.d.ts +1 -39
- package/dist/react/utils/deviceInfo.js +1 -163
- package/dist/react/utils/jwtDecoder.d.ts +1 -14
- package/dist/react/utils/jwtDecoder.js +1 -86
- package/dist/react/utils/tokenStorage.d.ts +1 -16
- package/dist/react/utils/tokenStorage.js +1 -53
- package/dist/v2/core/client.d.ts +92 -0
- package/dist/v2/core/client.js +386 -0
- package/dist/v2/core/config/environment.d.ts +22 -0
- package/dist/v2/core/config/environment.js +140 -0
- package/dist/v2/core/pathRemapping.js +61 -3
- package/dist/v2/core/resources/apiClient.d.ts +8 -0
- package/dist/v2/core/resources/apiClient.js +30 -9
- package/dist/v2/core/resources/funnel.d.ts +14 -0
- package/dist/v2/core/resources/payments.d.ts +23 -0
- package/dist/v2/core/types.d.ts +271 -0
- package/dist/v2/core/types.js +4 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
- package/dist/v2/core/utils/deviceInfo.js +162 -0
- package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
- package/dist/v2/core/utils/eventDispatcher.js +24 -0
- package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
- package/dist/v2/core/utils/jwtDecoder.js +85 -0
- package/dist/v2/core/utils/pluginConfig.js +6 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
- package/dist/v2/core/utils/tokenStorage.js +52 -0
- package/dist/v2/react/components/DebugDrawer.js +90 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +1 -1
- package/dist/v2/react/hooks/useFunnel.js +209 -32
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
- package/dist/v2/react/hooks/useISOData.js +4 -2
- package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
- package/dist/v2/react/hooks/useOffersQuery.js +164 -204
- package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
- package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
- package/dist/v2/react/providers/TagadaProvider.js +79 -673
- package/package.json +1 -1
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* Offers Hook using TanStack Query
|
|
3
3
|
* Handles offers with automatic caching
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { OffersResource } from '../../core/resources/offers';
|
|
8
8
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
-
import { usePluginConfig } from './usePluginConfig';
|
|
10
9
|
import { getGlobalApiClient } from './useApiQuery';
|
|
10
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
11
11
|
export function useOffersQuery(options = {}) {
|
|
12
|
-
const { offerIds = [], enabled = true, returnUrl } = options;
|
|
12
|
+
const { offerIds = [], enabled = true, returnUrl, orderId: defaultOrderId } = options;
|
|
13
13
|
const { storeId } = usePluginConfig();
|
|
14
14
|
const { isSessionInitialized, session } = useTagadaContext();
|
|
15
15
|
const _queryClient = useQueryClient();
|
|
16
16
|
// State for checkout sessions per offer (similar to postPurchases)
|
|
17
17
|
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
18
|
+
// Use ref to break dependency cycles in callbacks
|
|
19
|
+
const checkoutSessionsRef = useRef(checkoutSessions);
|
|
20
|
+
// Update ref on every render
|
|
21
|
+
checkoutSessionsRef.current = checkoutSessions;
|
|
18
22
|
// Create offers resource client
|
|
19
23
|
const offersResource = useMemo(() => {
|
|
20
24
|
try {
|
|
@@ -59,7 +63,9 @@ export function useOffersQuery(options = {}) {
|
|
|
59
63
|
isUpdatingSummary: false,
|
|
60
64
|
}
|
|
61
65
|
}));
|
|
66
|
+
return orderSummary;
|
|
62
67
|
}
|
|
68
|
+
return null;
|
|
63
69
|
}
|
|
64
70
|
catch (error) {
|
|
65
71
|
setCheckoutSessions(prev => ({
|
|
@@ -75,7 +81,7 @@ export function useOffersQuery(options = {}) {
|
|
|
75
81
|
// Create query key based on options
|
|
76
82
|
const queryKey = useMemo(() => ['offers', { storeId, offerIds }], [storeId, offerIds]);
|
|
77
83
|
// Use TanStack Query for fetching offers
|
|
78
|
-
const { data: offers = [], isLoading, error
|
|
84
|
+
const { data: offers = [], isLoading, error } = useQuery({
|
|
79
85
|
queryKey,
|
|
80
86
|
enabled: enabled && !!storeId && isSessionInitialized,
|
|
81
87
|
queryFn: async () => {
|
|
@@ -94,32 +100,7 @@ export function useOffersQuery(options = {}) {
|
|
|
94
100
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
95
101
|
refetchOnWindowFocus: false,
|
|
96
102
|
});
|
|
97
|
-
|
|
98
|
-
const createCheckoutSessionMutation = useMutation({
|
|
99
|
-
mutationFn: async ({ offerId, returnUrl }) => {
|
|
100
|
-
return await offersResource.createCheckoutSession(offerId, returnUrl);
|
|
101
|
-
},
|
|
102
|
-
onError: (_error) => {
|
|
103
|
-
// Error handling removed
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
const payOfferMutation = useMutation({
|
|
107
|
-
mutationFn: async ({ offerId, orderId }) => {
|
|
108
|
-
return await offersResource.payOffer(offerId, orderId);
|
|
109
|
-
},
|
|
110
|
-
onError: (_error) => {
|
|
111
|
-
// Error handling removed
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
const transformToCheckoutMutation = useMutation({
|
|
115
|
-
mutationFn: async ({ offerId, returnUrl }) => {
|
|
116
|
-
return await offersResource.transformToCheckout(offerId, returnUrl);
|
|
117
|
-
},
|
|
118
|
-
onError: (_error) => {
|
|
119
|
-
// Error handling removed
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
const payWithCheckoutSessionMutation = useMutation({
|
|
103
|
+
const { mutateAsync: payWithCheckoutSessionAsync } = useMutation({
|
|
123
104
|
mutationFn: async ({ checkoutSessionId, orderId }) => {
|
|
124
105
|
return await offersResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
125
106
|
},
|
|
@@ -127,7 +108,7 @@ export function useOffersQuery(options = {}) {
|
|
|
127
108
|
// Error handling removed
|
|
128
109
|
},
|
|
129
110
|
});
|
|
130
|
-
const
|
|
111
|
+
const { mutateAsync: initCheckoutSessionAsync } = useMutation({
|
|
131
112
|
mutationFn: async ({ offerId, orderId, customerId }) => {
|
|
132
113
|
return await offersResource.initCheckoutSession(offerId, orderId, customerId);
|
|
133
114
|
},
|
|
@@ -135,108 +116,125 @@ export function useOffersQuery(options = {}) {
|
|
|
135
116
|
// Error handling removed
|
|
136
117
|
},
|
|
137
118
|
});
|
|
138
|
-
// Helper functions (matching V1 useOffers exactly)
|
|
139
|
-
const getOffer = useCallback((offerId) => {
|
|
140
|
-
return offers.find((offer) => offer.id === offerId);
|
|
141
|
-
}, [offers]);
|
|
142
|
-
const getTotalValue = useCallback(() => {
|
|
143
|
-
return offers.reduce((total, offer) => {
|
|
144
|
-
const firstSummary = offer.summaries[0];
|
|
145
|
-
return total + (firstSummary?.totalAdjustedAmount || 0);
|
|
146
|
-
}, 0);
|
|
147
|
-
}, [offers]);
|
|
148
|
-
const getTotalSavings = useCallback(() => {
|
|
149
|
-
return offers.reduce((total, offer) => {
|
|
150
|
-
const firstSummary = offer.summaries[0];
|
|
151
|
-
return total + (firstSummary?.totalPromotionAmount || 0);
|
|
152
|
-
}, 0);
|
|
153
|
-
}, [offers]);
|
|
154
|
-
// Action functions
|
|
155
|
-
const createCheckoutSession = useCallback(async (offerId, options) => {
|
|
156
|
-
return await createCheckoutSessionMutation.mutateAsync({
|
|
157
|
-
offerId,
|
|
158
|
-
returnUrl: options?.returnUrl || returnUrl,
|
|
159
|
-
});
|
|
160
|
-
}, [createCheckoutSessionMutation, returnUrl]);
|
|
161
|
-
const payOffer = useCallback(async (offerId, orderId) => {
|
|
162
|
-
return await payOfferMutation.mutateAsync({ offerId, orderId });
|
|
163
|
-
}, [payOfferMutation]);
|
|
164
|
-
const transformToCheckout = useCallback(async (offerId, options) => {
|
|
165
|
-
return await transformToCheckoutMutation.mutateAsync({
|
|
166
|
-
offerId,
|
|
167
|
-
returnUrl: options?.returnUrl || returnUrl,
|
|
168
|
-
});
|
|
169
|
-
}, [transformToCheckoutMutation, returnUrl]);
|
|
170
119
|
const payWithCheckoutSession = useCallback(async (checkoutSessionId, orderId) => {
|
|
171
|
-
return await
|
|
172
|
-
}, [
|
|
120
|
+
return await payWithCheckoutSessionAsync({ checkoutSessionId, orderId });
|
|
121
|
+
}, [payWithCheckoutSessionAsync]);
|
|
173
122
|
const initCheckoutSession = useCallback(async (offerId, orderId, customerId) => {
|
|
174
123
|
// Use customerId from session context if not provided
|
|
175
124
|
const effectiveCustomerId = customerId || session?.customerId;
|
|
176
125
|
if (!effectiveCustomerId) {
|
|
177
126
|
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
178
127
|
}
|
|
179
|
-
return await
|
|
128
|
+
return await initCheckoutSessionAsync({
|
|
180
129
|
offerId,
|
|
181
130
|
orderId,
|
|
182
131
|
customerId: effectiveCustomerId
|
|
183
132
|
});
|
|
184
|
-
}, [
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
133
|
+
}, [initCheckoutSessionAsync, session?.customerId]);
|
|
134
|
+
const payOffer = useCallback(async (offerId, orderId) => {
|
|
135
|
+
const effectiveOrderId = orderId || defaultOrderId;
|
|
136
|
+
const effectiveCustomerId = session?.customerId;
|
|
137
|
+
if (!effectiveOrderId) {
|
|
138
|
+
throw new Error('Order ID is required for payment. Please provide it in the hook options or the function call.');
|
|
139
|
+
}
|
|
140
|
+
if (!effectiveCustomerId) {
|
|
141
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
142
|
+
}
|
|
143
|
+
// 1. Init session
|
|
144
|
+
const { checkoutSessionId } = await initCheckoutSession(offerId, effectiveOrderId, effectiveCustomerId);
|
|
145
|
+
// 2. Pay
|
|
146
|
+
await payWithCheckoutSession(checkoutSessionId, effectiveOrderId);
|
|
147
|
+
}, [initCheckoutSession, payWithCheckoutSession, defaultOrderId, session?.customerId]);
|
|
148
|
+
const preview = useCallback(async (offerId) => {
|
|
149
|
+
const effectiveOrderId = defaultOrderId;
|
|
150
|
+
const effectiveCustomerId = session?.customerId;
|
|
151
|
+
// Use ref to check current state without creating dependency
|
|
152
|
+
const currentSessions = checkoutSessionsRef.current;
|
|
153
|
+
// If we already have a summary in state, return it
|
|
154
|
+
if (currentSessions[offerId]?.orderSummary) {
|
|
155
|
+
return currentSessions[offerId].orderSummary;
|
|
156
|
+
}
|
|
157
|
+
// Prevent duplicate initialization if already has a session and is updating
|
|
158
|
+
if (currentSessions[offerId]?.checkoutSessionId && currentSessions[offerId]?.isUpdatingSummary) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// If we don't have orderId, fallback to static summary from offer object
|
|
162
|
+
// as we can't initialize a checkout session properly without orderId (for upsells)
|
|
163
|
+
if (!effectiveOrderId || !effectiveCustomerId) {
|
|
164
|
+
const offer = offers.find(o => o.id === offerId);
|
|
165
|
+
return offer?.summaries?.[0] || null;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
// If we already have a session ID, reuse it instead of creating a new one
|
|
169
|
+
let sessionId = currentSessions[offerId]?.checkoutSessionId;
|
|
170
|
+
if (!sessionId) {
|
|
171
|
+
const { checkoutSessionId } = await initCheckoutSession(offerId, effectiveOrderId, effectiveCustomerId);
|
|
172
|
+
sessionId = checkoutSessionId;
|
|
173
|
+
// Update state with session ID
|
|
174
|
+
setCheckoutSessions(prev => ({
|
|
175
|
+
...prev,
|
|
176
|
+
[offerId]: {
|
|
177
|
+
checkoutSessionId: sessionId,
|
|
178
|
+
orderSummary: null,
|
|
179
|
+
selectedVariants: {},
|
|
180
|
+
loadingVariants: {},
|
|
181
|
+
isUpdatingSummary: false,
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
218
184
|
}
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
185
|
+
// Fetch and return summary
|
|
186
|
+
return await fetchOrderSummary(offerId, sessionId);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
console.error("Failed to preview offer", e);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}, [offers, defaultOrderId, session?.customerId, initCheckoutSession, fetchOrderSummary]); // Removed checkoutSessions dependency
|
|
193
|
+
const getAvailableVariants = useCallback((offerId, productId) => {
|
|
194
|
+
const sessionState = checkoutSessions[offerId]; // This hook needs to react to state changes
|
|
195
|
+
if (!sessionState?.orderSummary?.options?.[productId])
|
|
196
|
+
return [];
|
|
197
|
+
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
198
|
+
variantId: variant.id,
|
|
199
|
+
variantName: variant.name,
|
|
200
|
+
variantSku: variant.sku,
|
|
201
|
+
variantDefault: variant.default,
|
|
202
|
+
variantExternalId: variant.externalVariantId,
|
|
203
|
+
priceId: variant.prices[0]?.id,
|
|
204
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
205
|
+
}));
|
|
206
|
+
}, [checkoutSessions]);
|
|
207
|
+
const isLoadingVariants = useCallback((offerId, productId) => {
|
|
208
|
+
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
209
|
+
}, [checkoutSessions]);
|
|
210
|
+
const selectVariant = useCallback(async (offerId, productId, variantId) => {
|
|
211
|
+
// Use ref for initial check to avoid dependency but we might need latest state for logic
|
|
212
|
+
// Actually for actions it's better to use ref or just dependency if action is not called in useEffect
|
|
213
|
+
const currentSessions = checkoutSessionsRef.current;
|
|
214
|
+
const sessionState = currentSessions[offerId];
|
|
215
|
+
if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
|
|
216
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
217
|
+
}
|
|
218
|
+
const sessionId = sessionState.checkoutSessionId;
|
|
219
|
+
// Set loading state for this specific variant
|
|
220
|
+
setCheckoutSessions(prev => ({
|
|
221
|
+
...prev,
|
|
222
|
+
[offerId]: {
|
|
223
|
+
...prev[offerId],
|
|
224
|
+
loadingVariants: {
|
|
225
|
+
...prev[offerId].loadingVariants,
|
|
226
|
+
[productId]: true,
|
|
230
227
|
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const sessionState =
|
|
237
|
-
if (!sessionState?.orderSummary?.options?.[productId])
|
|
238
|
-
|
|
239
|
-
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
try {
|
|
231
|
+
// We need to use the state from the ref to ensure we have the latest data without causing infinite loops
|
|
232
|
+
// if selectVariant was in a dependency array that triggered effects
|
|
233
|
+
const sessionState = checkoutSessionsRef.current[offerId];
|
|
234
|
+
if (!sessionState?.orderSummary?.options?.[productId]) {
|
|
235
|
+
throw new Error('No variants available for this product');
|
|
236
|
+
}
|
|
237
|
+
const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
|
|
240
238
|
variantId: variant.id,
|
|
241
239
|
variantName: variant.name,
|
|
242
240
|
variantSku: variant.sku,
|
|
@@ -245,98 +243,60 @@ export function useOffersQuery(options = {}) {
|
|
|
245
243
|
priceId: variant.prices[0]?.id,
|
|
246
244
|
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
247
245
|
}));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
247
|
+
if (!selectedVariant) {
|
|
248
|
+
throw new Error('Selected variant not found');
|
|
249
|
+
}
|
|
250
|
+
// Find the current item to get its quantity
|
|
251
|
+
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
252
|
+
if (!currentItem) {
|
|
253
|
+
throw new Error('Current item not found');
|
|
253
254
|
}
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
// Update selected variants state
|
|
256
|
+
setCheckoutSessions(prev => ({
|
|
257
|
+
...prev,
|
|
258
|
+
[offerId]: {
|
|
259
|
+
...prev[offerId],
|
|
260
|
+
selectedVariants: {
|
|
261
|
+
...prev[offerId].selectedVariants,
|
|
262
|
+
[productId]: variantId,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}));
|
|
266
|
+
// Update line items on the server
|
|
267
|
+
await offersResource.updateLineItems(sessionId, [
|
|
268
|
+
{
|
|
269
|
+
variantId: selectedVariant.variantId,
|
|
270
|
+
quantity: currentItem.quantity,
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
// Refetch order summary after successful line item update
|
|
274
|
+
return await fetchOrderSummary(offerId, sessionId);
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
// Clear loading state for this specific variant
|
|
256
278
|
setCheckoutSessions(prev => ({
|
|
257
279
|
...prev,
|
|
258
280
|
[offerId]: {
|
|
259
281
|
...prev[offerId],
|
|
260
282
|
loadingVariants: {
|
|
261
283
|
...prev[offerId].loadingVariants,
|
|
262
|
-
[productId]:
|
|
284
|
+
[productId]: false,
|
|
263
285
|
}
|
|
264
286
|
}
|
|
265
287
|
}));
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}));
|
|
280
|
-
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
281
|
-
if (!selectedVariant) {
|
|
282
|
-
throw new Error('Selected variant not found');
|
|
283
|
-
}
|
|
284
|
-
// Find the current item to get its quantity
|
|
285
|
-
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
286
|
-
if (!currentItem) {
|
|
287
|
-
throw new Error('Current item not found');
|
|
288
|
-
}
|
|
289
|
-
// Update selected variants state
|
|
290
|
-
setCheckoutSessions(prev => ({
|
|
291
|
-
...prev,
|
|
292
|
-
[offerId]: {
|
|
293
|
-
...prev[offerId],
|
|
294
|
-
selectedVariants: {
|
|
295
|
-
...prev[offerId].selectedVariants,
|
|
296
|
-
[productId]: variantId,
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}));
|
|
300
|
-
// Update line items on the server
|
|
301
|
-
await offersResource.updateLineItems(sessionId, [
|
|
302
|
-
{
|
|
303
|
-
variantId: selectedVariant.variantId,
|
|
304
|
-
quantity: currentItem.quantity,
|
|
305
|
-
},
|
|
306
|
-
]);
|
|
307
|
-
// Refetch order summary after successful line item update
|
|
308
|
-
await fetchOrderSummary(offerId, sessionId);
|
|
309
|
-
}
|
|
310
|
-
finally {
|
|
311
|
-
// Clear loading state for this specific variant
|
|
312
|
-
setCheckoutSessions(prev => ({
|
|
313
|
-
...prev,
|
|
314
|
-
[offerId]: {
|
|
315
|
-
...prev[offerId],
|
|
316
|
-
loadingVariants: {
|
|
317
|
-
...prev[offerId].loadingVariants,
|
|
318
|
-
[productId]: false,
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
getOrderSummary: (offerId) => {
|
|
325
|
-
return checkoutSessions[offerId]?.orderSummary ?? null;
|
|
326
|
-
},
|
|
327
|
-
isLoadingVariants: (offerId, productId) => {
|
|
328
|
-
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
329
|
-
},
|
|
330
|
-
isUpdatingOrderSummary: (offerId) => {
|
|
331
|
-
return checkoutSessions[offerId]?.isUpdatingSummary || false;
|
|
332
|
-
},
|
|
333
|
-
confirmPurchase: async (offerId, _options) => {
|
|
334
|
-
const sessionState = checkoutSessions[offerId];
|
|
335
|
-
if (!sessionState?.checkoutSessionId) {
|
|
336
|
-
throw new Error('Checkout session not initialized for this offer');
|
|
337
|
-
}
|
|
338
|
-
// Use the enhanced payWithCheckoutSession with proper metadata
|
|
339
|
-
await offersResource.payWithCheckoutSession(sessionState.checkoutSessionId);
|
|
340
|
-
},
|
|
288
|
+
}
|
|
289
|
+
}, [offersResource, fetchOrderSummary]); // Removed checkoutSessions dependency
|
|
290
|
+
return {
|
|
291
|
+
// Query data
|
|
292
|
+
offers,
|
|
293
|
+
isLoading,
|
|
294
|
+
error,
|
|
295
|
+
// Actions
|
|
296
|
+
payOffer,
|
|
297
|
+
preview,
|
|
298
|
+
getAvailableVariants,
|
|
299
|
+
selectVariant,
|
|
300
|
+
isLoadingVariants,
|
|
341
301
|
};
|
|
342
302
|
}
|
|
@@ -87,15 +87,28 @@ export function usePaymentQuery() {
|
|
|
87
87
|
},
|
|
88
88
|
onSuccess: (successPayment) => {
|
|
89
89
|
setIsLoading(false);
|
|
90
|
-
|
|
90
|
+
const response = {
|
|
91
91
|
paymentId: successPayment.id,
|
|
92
92
|
payment: successPayment,
|
|
93
|
-
|
|
93
|
+
// Extract order from payment if available (for funnel path resolution)
|
|
94
|
+
order: successPayment.order,
|
|
95
|
+
};
|
|
96
|
+
// Legacy callback (backwards compatibility)
|
|
97
|
+
options.onSuccess?.(response);
|
|
98
|
+
// Funnel-aligned callback (recommended)
|
|
99
|
+
options.onPaymentSuccess?.(response);
|
|
94
100
|
},
|
|
95
101
|
onFailure: (errorMsg) => {
|
|
96
102
|
setError(errorMsg);
|
|
97
103
|
setIsLoading(false);
|
|
104
|
+
// Legacy callback (backwards compatibility)
|
|
98
105
|
options.onFailure?.(errorMsg);
|
|
106
|
+
// Funnel-aligned callback (recommended)
|
|
107
|
+
options.onPaymentFailed?.({
|
|
108
|
+
code: 'PAYMENT_FAILED',
|
|
109
|
+
message: errorMsg,
|
|
110
|
+
payment,
|
|
111
|
+
});
|
|
99
112
|
},
|
|
100
113
|
});
|
|
101
114
|
}
|
|
@@ -105,22 +118,57 @@ export function usePaymentQuery() {
|
|
|
105
118
|
const errorMsg = _error instanceof Error ? _error.message : 'Failed to start 3DS challenge';
|
|
106
119
|
setError(errorMsg);
|
|
107
120
|
setIsLoading(false);
|
|
121
|
+
// Legacy callback (backwards compatibility)
|
|
108
122
|
options.onFailure?.(errorMsg);
|
|
123
|
+
// Funnel-aligned callback (recommended)
|
|
124
|
+
options.onPaymentFailed?.({
|
|
125
|
+
code: '3DS_CHALLENGE_FAILED',
|
|
126
|
+
message: errorMsg,
|
|
127
|
+
payment,
|
|
128
|
+
});
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
131
|
break;
|
|
112
132
|
case 'processor_auth':
|
|
113
133
|
case 'redirect': {
|
|
114
|
-
if (
|
|
134
|
+
// Only auto-redirect if explicitly enabled (disableAutoRedirect: false)
|
|
135
|
+
// Default behavior: disable redirects and let funnel orchestrator handle navigation
|
|
136
|
+
const shouldRedirect = options.disableAutoRedirect === false;
|
|
137
|
+
if (shouldRedirect && actionData.metadata?.redirect?.redirectUrl) {
|
|
115
138
|
window.location.href = actionData.metadata.redirect.redirectUrl;
|
|
116
139
|
}
|
|
140
|
+
else {
|
|
141
|
+
// If auto-redirect is disabled AND payment succeeded, call success callbacks
|
|
142
|
+
// This allows funnel orchestrator to handle navigation
|
|
143
|
+
if (payment.status === 'succeeded') {
|
|
144
|
+
setIsLoading(false);
|
|
145
|
+
const response = {
|
|
146
|
+
paymentId: payment.id,
|
|
147
|
+
payment,
|
|
148
|
+
// Extract order from payment if available (for funnel path resolution)
|
|
149
|
+
order: payment.order,
|
|
150
|
+
};
|
|
151
|
+
// Legacy callback (backwards compatibility)
|
|
152
|
+
options.onSuccess?.(response);
|
|
153
|
+
// Funnel-aligned callback (recommended)
|
|
154
|
+
options.onPaymentSuccess?.(response);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
117
157
|
break;
|
|
118
158
|
}
|
|
119
159
|
case 'error': {
|
|
120
160
|
const errorMsg = actionData.message || 'Payment processing failed';
|
|
161
|
+
const errorCode = actionData.errorCode || 'PAYMENT_FAILED';
|
|
121
162
|
setError(errorMsg);
|
|
122
163
|
setIsLoading(false);
|
|
164
|
+
// Legacy callback (backwards compatibility)
|
|
123
165
|
options.onFailure?.(errorMsg);
|
|
166
|
+
// Funnel-aligned callback (recommended)
|
|
167
|
+
options.onPaymentFailed?.({
|
|
168
|
+
code: errorCode,
|
|
169
|
+
message: errorMsg,
|
|
170
|
+
payment,
|
|
171
|
+
});
|
|
124
172
|
break;
|
|
125
173
|
}
|
|
126
174
|
}
|
|
@@ -147,7 +195,15 @@ export function usePaymentQuery() {
|
|
|
147
195
|
}
|
|
148
196
|
else if (response.payment.status === 'succeeded') {
|
|
149
197
|
setIsLoading(false);
|
|
150
|
-
|
|
198
|
+
// Ensure order is at response root (extract from payment if needed)
|
|
199
|
+
const successResponse = {
|
|
200
|
+
...response,
|
|
201
|
+
order: response.order || response.payment.order,
|
|
202
|
+
};
|
|
203
|
+
// Legacy callback (backwards compatibility)
|
|
204
|
+
options.onSuccess?.(successResponse);
|
|
205
|
+
// Funnel-aligned callback (recommended)
|
|
206
|
+
options.onPaymentSuccess?.(successResponse);
|
|
151
207
|
}
|
|
152
208
|
else {
|
|
153
209
|
// Start polling for payment status
|
|
@@ -157,15 +213,28 @@ export function usePaymentQuery() {
|
|
|
157
213
|
},
|
|
158
214
|
onSuccess: (payment) => {
|
|
159
215
|
setIsLoading(false);
|
|
160
|
-
|
|
216
|
+
const successResponse = {
|
|
161
217
|
paymentId: payment.id,
|
|
162
218
|
payment,
|
|
163
|
-
|
|
219
|
+
// Extract order from payment if available (for funnel path resolution)
|
|
220
|
+
order: payment.order,
|
|
221
|
+
};
|
|
222
|
+
// Legacy callback (backwards compatibility)
|
|
223
|
+
options.onSuccess?.(successResponse);
|
|
224
|
+
// Funnel-aligned callback (recommended)
|
|
225
|
+
options.onPaymentSuccess?.(successResponse);
|
|
164
226
|
},
|
|
165
227
|
onFailure: (errorMsg) => {
|
|
166
228
|
setError(errorMsg);
|
|
167
229
|
setIsLoading(false);
|
|
230
|
+
// Legacy callback (backwards compatibility)
|
|
168
231
|
options.onFailure?.(errorMsg);
|
|
232
|
+
// Funnel-aligned callback (recommended)
|
|
233
|
+
options.onPaymentFailed?.({
|
|
234
|
+
code: 'PAYMENT_FAILED',
|
|
235
|
+
message: errorMsg,
|
|
236
|
+
payment: response.payment,
|
|
237
|
+
});
|
|
169
238
|
},
|
|
170
239
|
});
|
|
171
240
|
}
|
|
@@ -175,7 +244,13 @@ export function usePaymentQuery() {
|
|
|
175
244
|
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
176
245
|
setError(errorMsg);
|
|
177
246
|
setIsLoading(false);
|
|
247
|
+
// Legacy callback (backwards compatibility)
|
|
178
248
|
options.onFailure?.(errorMsg);
|
|
249
|
+
// Funnel-aligned callback (recommended)
|
|
250
|
+
options.onPaymentFailed?.({
|
|
251
|
+
code: 'PAYMENT_PROCESSING_ERROR',
|
|
252
|
+
message: errorMsg,
|
|
253
|
+
});
|
|
179
254
|
throw _error;
|
|
180
255
|
}
|
|
181
256
|
}, [paymentsResource, handlePaymentAction, startPolling]);
|
|
@@ -205,7 +280,13 @@ export function usePaymentQuery() {
|
|
|
205
280
|
setIsLoading(false);
|
|
206
281
|
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
207
282
|
setError(errorMsg);
|
|
283
|
+
// Legacy callback (backwards compatibility)
|
|
208
284
|
options.onFailure?.(errorMsg);
|
|
285
|
+
// Funnel-aligned callback (recommended)
|
|
286
|
+
options.onPaymentFailed?.({
|
|
287
|
+
code: 'CARD_PAYMENT_ERROR',
|
|
288
|
+
message: errorMsg,
|
|
289
|
+
});
|
|
209
290
|
throw _error;
|
|
210
291
|
}
|
|
211
292
|
}, [createCardPaymentInstrument, createSession, processPaymentDirect]);
|
|
@@ -223,7 +304,13 @@ export function usePaymentQuery() {
|
|
|
223
304
|
setIsLoading(false);
|
|
224
305
|
const errorMsg = _error instanceof Error ? _error.message : 'Apple Pay payment failed';
|
|
225
306
|
setError(errorMsg);
|
|
307
|
+
// Legacy callback (backwards compatibility)
|
|
226
308
|
options.onFailure?.(errorMsg);
|
|
309
|
+
// Funnel-aligned callback (recommended)
|
|
310
|
+
options.onPaymentFailed?.({
|
|
311
|
+
code: 'APPLE_PAY_ERROR',
|
|
312
|
+
message: errorMsg,
|
|
313
|
+
});
|
|
227
314
|
throw _error;
|
|
228
315
|
}
|
|
229
316
|
}, [createApplePayPaymentInstrument, processPaymentDirect]);
|
|
@@ -238,7 +325,13 @@ export function usePaymentQuery() {
|
|
|
238
325
|
setIsLoading(false);
|
|
239
326
|
const errorMsg = _error instanceof Error ? _error.message : 'Payment failed';
|
|
240
327
|
setError(errorMsg);
|
|
328
|
+
// Legacy callback (backwards compatibility)
|
|
241
329
|
options.onFailure?.(errorMsg);
|
|
330
|
+
// Funnel-aligned callback (recommended)
|
|
331
|
+
options.onPaymentFailed?.({
|
|
332
|
+
code: 'PAYMENT_INSTRUMENT_ERROR',
|
|
333
|
+
message: errorMsg,
|
|
334
|
+
});
|
|
242
335
|
throw _error;
|
|
243
336
|
}
|
|
244
337
|
}, [processPaymentDirect]);
|