@tagadapay/plugin-sdk 3.0.3 → 3.0.12

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 (49) hide show
  1. package/build-cdn.js +113 -0
  2. package/dist/config/basisTheory.d.ts +26 -0
  3. package/dist/config/basisTheory.js +29 -0
  4. package/dist/external-tracker.js +5072 -0
  5. package/dist/external-tracker.min.js +11 -0
  6. package/dist/external-tracker.min.js.map +7 -0
  7. package/dist/react/config/payment.d.ts +8 -8
  8. package/dist/react/config/payment.js +17 -21
  9. package/dist/react/hooks/useApplePay.js +1 -1
  10. package/dist/react/hooks/usePayment.js +1 -3
  11. package/dist/react/hooks/useThreeds.js +2 -2
  12. package/dist/v2/core/client.d.ts +30 -3
  13. package/dist/v2/core/client.js +326 -8
  14. package/dist/v2/core/config/environment.d.ts +16 -3
  15. package/dist/v2/core/config/environment.js +72 -3
  16. package/dist/v2/core/funnelClient.d.ts +4 -0
  17. package/dist/v2/core/funnelClient.js +106 -4
  18. package/dist/v2/core/resources/funnel.d.ts +22 -0
  19. package/dist/v2/core/resources/offers.d.ts +64 -3
  20. package/dist/v2/core/resources/offers.js +112 -10
  21. package/dist/v2/core/resources/postPurchases.js +4 -1
  22. package/dist/v2/core/utils/configHotReload.d.ts +39 -0
  23. package/dist/v2/core/utils/configHotReload.js +75 -0
  24. package/dist/v2/core/utils/eventBus.d.ts +11 -0
  25. package/dist/v2/core/utils/eventBus.js +34 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +14 -5
  27. package/dist/v2/core/utils/pluginConfig.js +74 -59
  28. package/dist/v2/core/utils/previewMode.d.ts +114 -0
  29. package/dist/v2/core/utils/previewMode.js +379 -0
  30. package/dist/v2/core/utils/sessionStorage.d.ts +5 -0
  31. package/dist/v2/core/utils/sessionStorage.js +22 -0
  32. package/dist/v2/index.d.ts +4 -1
  33. package/dist/v2/index.js +3 -1
  34. package/dist/v2/react/components/DebugDrawer.js +68 -46
  35. package/dist/v2/react/hooks/useOfferQuery.js +50 -17
  36. package/dist/v2/react/hooks/usePaymentQuery.js +1 -3
  37. package/dist/v2/react/hooks/usePreviewOffer.d.ts +84 -0
  38. package/dist/v2/react/hooks/usePreviewOffer.js +290 -0
  39. package/dist/v2/react/hooks/useThreeds.js +2 -2
  40. package/dist/v2/react/index.d.ts +2 -0
  41. package/dist/v2/react/index.js +1 -0
  42. package/dist/v2/react/providers/TagadaProvider.js +49 -32
  43. package/dist/v2/standalone/external-tracker.d.ts +119 -0
  44. package/dist/v2/standalone/external-tracker.js +260 -0
  45. package/dist/v2/standalone/index.d.ts +2 -0
  46. package/dist/v2/standalone/index.js +6 -0
  47. package/package.json +11 -3
  48. package/dist/v2/react/hooks/useOffersQuery.d.ts +0 -12
  49. package/dist/v2/react/hooks/useOffersQuery.js +0 -404
@@ -15,9 +15,12 @@ import { useTagadaContext } from '../providers/TagadaProvider';
15
15
  import { getGlobalApiClient } from './useApiQuery';
16
16
  import { usePluginConfig } from './usePluginConfig';
17
17
  export function useOfferQuery(options) {
18
- const { offerId, enabled = true, mainOrderId } = options;
18
+ const { offerId, enabled = true, mainOrderId: rawMainOrderId } = options;
19
19
  const { storeId } = usePluginConfig();
20
20
  const { isSessionInitialized, session } = useTagadaContext();
21
+ // 🎯 Normalize mainOrderId: treat undefined and empty string as the same
22
+ // This prevents effect re-runs when value changes from undefined to ''
23
+ const mainOrderId = rawMainOrderId || undefined;
21
24
  // ─────────────────────────────────────────────────────────────────────────
22
25
  // State
23
26
  // ─────────────────────────────────────────────────────────────────────────
@@ -140,17 +143,28 @@ export function useOfferQuery(options) {
140
143
  });
141
144
  }, [initCheckoutSessionAsync, session?.customerId, isSessionInitialized]);
142
145
  // ─────────────────────────────────────────────────────────────────────────
143
- // 2. Auto-initialize checkout session when mainOrderId is provided
146
+ // 2. Auto-initialize checkout session when offer is loaded
147
+ // (mainOrderId is optional - used for post-purchase upsells, not needed for standalone offers)
144
148
  // ─────────────────────────────────────────────────────────────────────────
145
149
  useEffect(() => {
146
- if (!offer || !mainOrderId || !isSessionInitialized)
150
+ // Only require offer and session to be ready (mainOrderId is optional)
151
+ if (!offer || !isSessionInitialized) {
147
152
  return;
148
- if (checkoutSession.checkoutSessionId || isInitializing || hasInitializedRef.current)
153
+ }
154
+ if (checkoutSession.checkoutSessionId || isInitializing) {
155
+ return;
156
+ }
157
+ // 🎯 CRITICAL: Check and set initialization flag atomically
158
+ // This ensures only ONE effect can proceed with initialization
159
+ // Once set to true, it stays true (never reset) to prevent duplicate inits
160
+ if (hasInitializedRef.current) {
149
161
  return;
162
+ }
163
+ hasInitializedRef.current = true;
150
164
  const initSession = async () => {
151
- hasInitializedRef.current = true;
152
165
  setIsInitializing(true);
153
166
  try {
167
+ // mainOrderId is optional - pass undefined if not provided
154
168
  const result = await initCheckoutSession(offerId, mainOrderId);
155
169
  setCheckoutSession(prev => ({
156
170
  ...prev,
@@ -160,7 +174,7 @@ export function useOfferQuery(options) {
160
174
  await fetchOrderSummary(result.checkoutSessionId);
161
175
  }
162
176
  catch (err) {
163
- console.error('Failed to init checkout session:', err);
177
+ console.error('[useOfferQuery] Failed to init checkout session:', err);
164
178
  hasInitializedRef.current = false; // Allow retry on error
165
179
  }
166
180
  finally {
@@ -198,7 +212,8 @@ export function useOfferQuery(options) {
198
212
  const product = variant?.product;
199
213
  if (!variant || !product)
200
214
  continue;
201
- const currency = offer.summaries?.[0]?.currency || 'USD';
215
+ // Currency is determined from the checkout session, not from the offer
216
+ const currency = checkoutSession.orderSummary?.currency || 'USD';
202
217
  const currencyOption = price?.currencyOptions?.[currency];
203
218
  const unitAmount = currencyOption?.amount ?? variant.price ?? 0;
204
219
  items.push({
@@ -282,21 +297,39 @@ export function useOfferQuery(options) {
282
297
  const orderSummary = checkoutSession.orderSummary;
283
298
  if (orderSummary?.options?.[productId]) {
284
299
  const currency = orderSummary.currency || 'USD';
285
- return orderSummary.options[productId].map((variant) => ({
286
- variantId: variant.id,
287
- variantName: variant.name,
288
- sku: variant.sku || null,
289
- imageUrl: variant.imageUrl || null,
290
- unitAmount: variant.prices?.[0]?.currencyOptions?.[currency]?.amount ?? 0,
291
- currency,
292
- isDefault: variant.default ?? false,
293
- }));
300
+ // Find the active line item for this product to get the correct priceId
301
+ const activeLineItem = orderSummary.items?.find((item) => item.productId === productId);
302
+ const activePriceId = activeLineItem?.priceId;
303
+ return orderSummary.options[productId].map((variant) => {
304
+ // Try to find the price that matches the active priceId, otherwise use first price
305
+ let unitAmount = 0;
306
+ if (activePriceId && variant.prices) {
307
+ const matchingPrice = variant.prices.find((p) => p.id === activePriceId);
308
+ if (matchingPrice?.currencyOptions?.[currency]?.amount) {
309
+ unitAmount = matchingPrice.currencyOptions[currency].amount;
310
+ }
311
+ }
312
+ // Fallback to first price if no match found
313
+ if (unitAmount === 0 && variant.prices?.[0]?.currencyOptions?.[currency]?.amount) {
314
+ unitAmount = variant.prices[0].currencyOptions[currency].amount;
315
+ }
316
+ return {
317
+ variantId: variant.id,
318
+ variantName: variant.name,
319
+ sku: variant.sku || null,
320
+ imageUrl: variant.imageUrl || null,
321
+ unitAmount,
322
+ currency,
323
+ isDefault: variant.default ?? false,
324
+ };
325
+ });
294
326
  }
295
327
  // Fallback to static offer data
296
328
  if (!offer?.offerLineItems)
297
329
  return [];
298
330
  const variants = [];
299
- const currency = offer.summaries?.[0]?.currency || 'USD';
331
+ // Currency is determined from the checkout session, not from the offer
332
+ const currency = checkoutSession.orderSummary?.currency || 'USD';
300
333
  for (const item of offer.offerLineItems) {
301
334
  const price = item.price;
302
335
  const variant = price?.variant;
@@ -28,10 +28,8 @@ export function usePaymentQuery() {
28
28
  const { createSession, startChallenge } = useThreeds();
29
29
  // Track challenge in progress to prevent multiple challenges
30
30
  const challengeInProgressRef = useRef(false);
31
- // Stabilize environment value to prevent re-renders
32
- const currentEnvironment = useMemo(() => environment?.environment || 'local', [environment?.environment]);
33
31
  // Get API key from embedded configuration with proper environment detection
34
- const apiKey = useMemo(() => getBasisTheoryApiKey(currentEnvironment), [currentEnvironment]);
32
+ const apiKey = useMemo(() => getBasisTheoryApiKey(), []); // Auto-detects environment
35
33
  // Initialize BasisTheory using React wrapper
36
34
  const { bt: basisTheory, error: btError } = useBasisTheory(apiKey, {
37
35
  elements: false,
@@ -0,0 +1,84 @@
1
+ /**
2
+ * usePreviewOffer - Lightweight offer preview without checkout sessions
3
+ *
4
+ * This hook provides a fast, client-side offer preview by:
5
+ * 1. Fetching offer data with pre-computed summaries
6
+ * 2. Allowing variant/quantity selection
7
+ * 3. Recalculating totals on-the-fly without backend calls
8
+ * 4. NO checkout session creation - perfect for browsing/previewing
9
+ */
10
+ import { Offer } from '../../core/resources/offers';
11
+ export interface PreviewLineItemSelection {
12
+ lineItemId: string;
13
+ variantId: string;
14
+ quantity: number;
15
+ priceId?: string;
16
+ }
17
+ export interface PreviewOfferSummary {
18
+ items: Array<{
19
+ id: string;
20
+ productId: string;
21
+ variantId: string;
22
+ priceId: string;
23
+ productName: string;
24
+ variantName: string;
25
+ imageUrl: string | null;
26
+ quantity: number;
27
+ unitAmount: number;
28
+ amount: number;
29
+ adjustedAmount: number;
30
+ currency: string;
31
+ }>;
32
+ totalAmount: number;
33
+ totalAdjustedAmount: number;
34
+ totalPromotionAmount: number;
35
+ currency: string;
36
+ options: Record<string, Array<{
37
+ variantId: string;
38
+ variantName: string;
39
+ sku: string | null;
40
+ imageUrl: string | null;
41
+ prices: Array<{
42
+ id: string;
43
+ currencyOptions: Record<string, {
44
+ amount: number;
45
+ currency: string;
46
+ }>;
47
+ }>;
48
+ default: boolean;
49
+ }>>;
50
+ }
51
+ export interface UsePreviewOfferOptions {
52
+ offerId: string;
53
+ currency?: string;
54
+ initialSelections?: Record<string, PreviewLineItemSelection>;
55
+ }
56
+ export interface UsePreviewOfferResult {
57
+ offer: Offer | null;
58
+ isLoading: boolean;
59
+ isFetching: boolean;
60
+ isPaying: boolean;
61
+ error: Error | null;
62
+ summary: PreviewOfferSummary | null;
63
+ selections: Record<string, PreviewLineItemSelection>;
64
+ selectVariant: (lineItemId: string, variantId: string) => void;
65
+ updateQuantity: (lineItemId: string, quantity: number) => void;
66
+ selectVariantByProduct: (productId: string, variantId: string) => void;
67
+ updateQuantityByProduct: (productId: string, quantity: number) => void;
68
+ getAvailableVariants: (productId: string) => Array<{
69
+ variantId: string;
70
+ variantName: string;
71
+ imageUrl: string | null;
72
+ unitAmount: number;
73
+ currency: string;
74
+ }>;
75
+ pay: (mainOrderId?: string) => Promise<{
76
+ checkoutUrl: string;
77
+ }>;
78
+ toCheckout: (mainOrderId?: string) => Promise<{
79
+ checkoutSessionId?: string;
80
+ checkoutToken?: string;
81
+ checkoutUrl: string;
82
+ }>;
83
+ }
84
+ export declare function usePreviewOffer(options: UsePreviewOfferOptions): UsePreviewOfferResult;
@@ -0,0 +1,290 @@
1
+ /**
2
+ * usePreviewOffer - Lightweight offer preview without checkout sessions
3
+ *
4
+ * This hook provides a fast, client-side offer preview by:
5
+ * 1. Fetching offer data with pre-computed summaries
6
+ * 2. Allowing variant/quantity selection
7
+ * 3. Recalculating totals on-the-fly without backend calls
8
+ * 4. NO checkout session creation - perfect for browsing/previewing
9
+ */
10
+ import { useCallback, useEffect, useMemo, useState } from 'react';
11
+ import { useQuery } from '@tanstack/react-query';
12
+ import { OffersResource } from '../../core/resources/offers';
13
+ import { getGlobalApiClient } from './useApiQuery';
14
+ export function usePreviewOffer(options) {
15
+ const { offerId, currency: requestedCurrency = 'USD', initialSelections = {} } = options;
16
+ const offersResource = useMemo(() => new OffersResource(getGlobalApiClient()), []);
17
+ // Track user selections (variant + quantity per product)
18
+ const [selections, setSelections] = useState(initialSelections);
19
+ const [isPaying, setIsPaying] = useState(false);
20
+ // Build lineItems array for API call
21
+ const lineItemsForPreview = useMemo(() => {
22
+ if (Object.keys(selections).length === 0)
23
+ return undefined;
24
+ return Object.values(selections).map((selection) => {
25
+ const item = {
26
+ variantId: selection.variantId,
27
+ quantity: selection.quantity,
28
+ };
29
+ // Include lineItemId for precise matching
30
+ if (selection.lineItemId) {
31
+ item.lineItemId = selection.lineItemId;
32
+ }
33
+ return item;
34
+ });
35
+ }, [selections]);
36
+ // 🎯 ONE API CALL - Fetch offer + preview in one request
37
+ const { data, isLoading, isFetching, error } = useQuery({
38
+ queryKey: ['offer-preview', offerId, requestedCurrency, lineItemsForPreview],
39
+ queryFn: async () => {
40
+ console.log('🔍 [usePreviewOffer] Fetching preview:', {
41
+ offerId,
42
+ currency: requestedCurrency,
43
+ lineItems: lineItemsForPreview,
44
+ });
45
+ const result = await offersResource.previewOffer(offerId, requestedCurrency, lineItemsForPreview);
46
+ console.log('✅ [usePreviewOffer] Preview result:', result);
47
+ // Extract offer and preview from unified response
48
+ const offer = result.offer || null;
49
+ const preview = result.preview || result; // Backwards compatibility
50
+ // Transform preview to expected format
51
+ const summary = {
52
+ items: (preview.items || []).map((item) => ({
53
+ id: item.id,
54
+ productId: item.productId,
55
+ variantId: item.variantId,
56
+ priceId: item.priceId,
57
+ productName: item.product?.name || item.orderLineItemProduct?.name || '',
58
+ variantName: item.variant?.name || item.orderLineItemVariant?.name || '',
59
+ imageUrl: item.variant?.imageUrl || item.orderLineItemVariant?.imageUrl || null,
60
+ quantity: item.quantity,
61
+ unitAmount: item.unitAmount,
62
+ amount: item.amount,
63
+ adjustedAmount: item.adjustedAmount,
64
+ currency: item.currency,
65
+ })),
66
+ totalAmount: preview.totalAmount || 0,
67
+ totalAdjustedAmount: preview.totalAdjustedAmount || 0,
68
+ totalPromotionAmount: preview.totalPromotionAmount || 0,
69
+ currency: preview.currency || requestedCurrency,
70
+ options: preview.options || {},
71
+ };
72
+ return { offer, summary };
73
+ },
74
+ enabled: !!offerId,
75
+ staleTime: 0, // Always refetch, no cache
76
+ gcTime: 5000, // Keep in cache for 5s during transitions
77
+ placeholderData: (previousData) => previousData, // Keep previous data during refetch
78
+ refetchOnMount: 'always', // Always refetch on mount
79
+ retry: false, // Don't retry on error
80
+ retryOnMount: false,
81
+ });
82
+ const offer = data?.offer || null;
83
+ const summary = data?.summary || null;
84
+ // Initialize selections from preview response on first load
85
+ useEffect(() => {
86
+ if (summary && Object.keys(selections).length === 0) {
87
+ const initialSelections = {};
88
+ summary.items.forEach((item) => {
89
+ // Use lineItemId as key to support same product with different variants
90
+ initialSelections[item.id] = {
91
+ lineItemId: item.id,
92
+ variantId: item.variantId,
93
+ quantity: item.quantity,
94
+ priceId: item.priceId,
95
+ };
96
+ });
97
+ console.log('🎬 [usePreviewOffer] Initialized selections:', initialSelections);
98
+ setSelections(initialSelections);
99
+ }
100
+ }, [summary, selections]);
101
+ console.log('🎁 [usePreviewOffer] State:', {
102
+ offerId,
103
+ isLoading,
104
+ isFetching,
105
+ hasData: !!data,
106
+ hasOffer: !!offer,
107
+ hasSummary: !!summary,
108
+ selectionsCount: Object.keys(selections).length,
109
+ error: error?.message,
110
+ });
111
+ // ===== Precise methods (by lineItemId) =====
112
+ // Select a variant for a specific line item
113
+ const selectVariant = useCallback((lineItemId, variantId) => {
114
+ const item = summary?.items.find((i) => i.id === lineItemId);
115
+ if (!item)
116
+ return;
117
+ const variants = summary?.options?.[item.productId];
118
+ if (!variants)
119
+ return;
120
+ const selectedVariant = variants.find((v) => (v.id || v.variantId) === variantId);
121
+ if (!selectedVariant)
122
+ return;
123
+ const priceId = selectedVariant.prices?.[0]?.id;
124
+ setSelections(prev => ({
125
+ ...prev,
126
+ [lineItemId]: {
127
+ lineItemId,
128
+ variantId,
129
+ quantity: prev[lineItemId]?.quantity || item.quantity,
130
+ priceId,
131
+ },
132
+ }));
133
+ }, [summary]);
134
+ // Update quantity for a specific line item
135
+ const updateQuantity = useCallback((lineItemId, quantity) => {
136
+ if (quantity < 1)
137
+ return;
138
+ const item = summary?.items.find((i) => i.id === lineItemId);
139
+ if (!item)
140
+ return;
141
+ setSelections(prev => ({
142
+ ...prev,
143
+ [lineItemId]: {
144
+ lineItemId,
145
+ variantId: prev[lineItemId]?.variantId || item.variantId,
146
+ quantity,
147
+ priceId: prev[lineItemId]?.priceId || item.priceId,
148
+ },
149
+ }));
150
+ }, [summary]);
151
+ // ===== Simple methods (by productId - affects all items with this product) =====
152
+ // Select a variant for all line items with this product
153
+ const selectVariantByProduct = useCallback((productId, variantId) => {
154
+ if (!summary?.options?.[productId])
155
+ return;
156
+ const variants = summary.options[productId];
157
+ const selectedVariant = variants.find((v) => (v.id || v.variantId) === variantId);
158
+ if (!selectedVariant)
159
+ return;
160
+ const priceId = selectedVariant.prices?.[0]?.id;
161
+ setSelections(prev => {
162
+ const updated = { ...prev };
163
+ // Update all line items with this productId
164
+ summary.items.forEach((item) => {
165
+ if (item.productId === productId) {
166
+ updated[item.id] = {
167
+ lineItemId: item.id,
168
+ variantId,
169
+ quantity: prev[item.id]?.quantity || item.quantity,
170
+ priceId,
171
+ };
172
+ }
173
+ });
174
+ return updated;
175
+ });
176
+ }, [summary]);
177
+ // Update quantity for all line items with this product
178
+ const updateQuantityByProduct = useCallback((productId, quantity) => {
179
+ if (quantity < 1)
180
+ return;
181
+ setSelections(prev => {
182
+ const updated = { ...prev };
183
+ // Update all line items with this productId
184
+ summary?.items.forEach((item) => {
185
+ if (item.productId === productId) {
186
+ updated[item.id] = {
187
+ lineItemId: item.id,
188
+ variantId: prev[item.id]?.variantId || item.variantId,
189
+ quantity,
190
+ priceId: prev[item.id]?.priceId || item.priceId,
191
+ };
192
+ }
193
+ });
194
+ return updated;
195
+ });
196
+ }, [summary]);
197
+ // Get available variants for a product
198
+ const getAvailableVariants = useCallback((productId) => {
199
+ if (!summary?.options?.[productId])
200
+ return [];
201
+ const currentItem = summary.items.find((i) => i.productId === productId);
202
+ const activePriceId = currentItem?.priceId;
203
+ const currency = summary.currency || requestedCurrency;
204
+ return summary.options[productId].map((variant) => {
205
+ // Find matching price or use first
206
+ let unitAmount = 0;
207
+ if (activePriceId && variant.prices) {
208
+ const matchingPrice = variant.prices.find((p) => p.id === activePriceId);
209
+ if (matchingPrice?.currencyOptions?.[currency]) {
210
+ unitAmount = matchingPrice.currencyOptions[currency].amount;
211
+ }
212
+ }
213
+ // Fallback to first price
214
+ if (unitAmount === 0 && variant.prices?.[0]?.currencyOptions?.[currency]) {
215
+ unitAmount = variant.prices[0].currencyOptions[currency].amount;
216
+ }
217
+ return {
218
+ variantId: variant.id || variant.variantId,
219
+ variantName: variant.name || variant.variantName,
220
+ imageUrl: variant.imageUrl,
221
+ unitAmount,
222
+ currency,
223
+ };
224
+ });
225
+ }, [summary, requestedCurrency]);
226
+ // Pay for the offer with current selections
227
+ const pay = useCallback(async (mainOrderId) => {
228
+ if (isPaying) {
229
+ throw new Error('Payment already in progress');
230
+ }
231
+ setIsPaying(true);
232
+ try {
233
+ const result = await offersResource.payPreviewedOffer(offerId, requestedCurrency, lineItemsForPreview, typeof window !== 'undefined' ? window.location.href : undefined, mainOrderId);
234
+ console.log('[usePreviewOffer] Payment initiated:', result);
235
+ return {
236
+ checkoutUrl: result.checkout.checkoutUrl,
237
+ };
238
+ }
239
+ catch (error) {
240
+ console.error('[usePreviewOffer] Payment failed:', error);
241
+ throw error;
242
+ }
243
+ finally {
244
+ setIsPaying(false);
245
+ }
246
+ }, [offerId, requestedCurrency, lineItemsForPreview, offersResource, isPaying]);
247
+ // Create checkout session without paying (for landing pages)
248
+ const toCheckout = useCallback(async (mainOrderId) => {
249
+ if (isPaying) {
250
+ throw new Error('Operation already in progress');
251
+ }
252
+ setIsPaying(true);
253
+ try {
254
+ const result = await offersResource.toCheckout(offerId, requestedCurrency, lineItemsForPreview, typeof window !== 'undefined' ? window.location.href : undefined, mainOrderId);
255
+ console.log('[usePreviewOffer] Checkout session created:', result);
256
+ return {
257
+ checkoutSessionId: result.checkoutSessionId,
258
+ checkoutToken: result.checkoutToken,
259
+ checkoutUrl: result.checkoutUrl,
260
+ };
261
+ }
262
+ catch (error) {
263
+ console.error('[usePreviewOffer] Checkout creation failed:', error);
264
+ throw error;
265
+ }
266
+ finally {
267
+ setIsPaying(false);
268
+ }
269
+ }, [offerId, requestedCurrency, lineItemsForPreview, offersResource, isPaying]);
270
+ return {
271
+ offer,
272
+ isLoading,
273
+ isFetching,
274
+ isPaying,
275
+ error: error,
276
+ summary,
277
+ selections,
278
+ // Precise methods
279
+ selectVariant,
280
+ updateQuantity,
281
+ // Simple methods
282
+ selectVariantByProduct,
283
+ updateQuantityByProduct,
284
+ // Helpers
285
+ getAvailableVariants,
286
+ // Payment
287
+ pay,
288
+ toCheckout,
289
+ };
290
+ }
@@ -47,7 +47,7 @@ export function useThreeds(options = {}) {
47
47
  throw new Error('BasisTheory3ds not loaded yet');
48
48
  }
49
49
  // Use the same API key approach as the working CMS version
50
- const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
50
+ const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
51
51
  const bt3ds = basisTheory3ds(apiKey);
52
52
  const session = await bt3ds.createSession({
53
53
  tokenId: paymentInstrument.token,
@@ -91,7 +91,7 @@ export function useThreeds(options = {}) {
91
91
  throw new Error('BasisTheory3ds not loaded yet');
92
92
  }
93
93
  // Use the same API key approach as the working CMS version
94
- const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
94
+ const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
95
95
  if (!apiKey) {
96
96
  throw new Error('BasisTheory API key is not set');
97
97
  }
@@ -27,6 +27,7 @@ export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
27
27
  export { useCurrency } from './hooks/useCurrency';
28
28
  export { useDiscountsQuery as useDiscounts } from './hooks/useDiscountsQuery';
29
29
  export { useOfferQuery as useOffer } from './hooks/useOfferQuery';
30
+ export { usePreviewOffer } from './hooks/usePreviewOffer';
30
31
  export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
31
32
  export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
32
33
  export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';
@@ -64,6 +65,7 @@ export type { FunnelAction, FunnelNavigationAction, FunnelNavigationResult, Simp
64
65
  export type { FunnelContextValue } from './hooks/useFunnel';
65
66
  export type { UseFunnelOptions as UseFunnelLegacyOptions, UseFunnelResult as UseFunnelLegacyResult } from './hooks/useFunnelLegacy';
66
67
  export type { AvailableVariant, LineItemSelection, OfferLineItem, OfferPreviewSummary, UseOfferQueryOptions as UseOfferOptions, UseOfferQueryResult as UseOfferResult } from './hooks/useOfferQuery';
68
+ export type { PreviewOfferSummary, UsePreviewOfferOptions, UsePreviewOfferResult } from './hooks/usePreviewOffer';
67
69
  export type { UseOrderBumpQueryOptions as UseOrderBumpOptions, UseOrderBumpQueryResult as UseOrderBumpResult } from './hooks/useOrderBumpQuery';
68
70
  export type { UseOrderQueryOptions as UseOrderOptions, UseOrderQueryResult as UseOrderResult } from './hooks/useOrderQuery';
69
71
  export type { PaymentHook as UsePaymentResult } from './hooks/usePaymentQuery';
@@ -30,6 +30,7 @@ export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
30
30
  export { useCurrency } from './hooks/useCurrency';
31
31
  export { useDiscountsQuery as useDiscounts } from './hooks/useDiscountsQuery';
32
32
  export { useOfferQuery as useOffer } from './hooks/useOfferQuery';
33
+ export { usePreviewOffer } from './hooks/usePreviewOffer';
33
34
  export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
34
35
  export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
35
36
  export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';