@tagadapay/plugin-sdk 1.0.9 → 1.0.11
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/payment.d.ts +7 -1
- package/dist/react/config/payment.js +28 -11
- package/dist/react/hooks/useCheckout.d.ts +22 -0
- package/dist/react/hooks/useCheckout.js +137 -1
- package/dist/react/hooks/useOrderBump.d.ts +30 -0
- package/dist/react/hooks/useOrderBump.js +97 -0
- package/dist/react/hooks/usePayment.js +25 -49
- package/dist/react/hooks/useThreeds.d.ts +7 -9
- package/dist/react/hooks/useThreeds.js +25 -27
- package/dist/react/hooks/useThreedsModal.d.ts +12 -15
- package/dist/react/hooks/useThreedsModal.js +26 -11
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +1 -0
- package/dist/react/providers/TagadaProvider.d.ts +2 -8
- package/dist/react/providers/TagadaProvider.js +3 -46
- package/package.json +3 -3
|
@@ -18,6 +18,12 @@ export declare const PAYMENT_CONFIGS: Record<string, PaymentConfig>;
|
|
|
18
18
|
export declare function getPaymentConfig(environment?: string): PaymentConfig;
|
|
19
19
|
/**
|
|
20
20
|
* Get BasisTheory API key for current environment
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* Behavior:
|
|
23
|
+
* - LOCAL/DEVELOPMENT: Always uses embedded test key (key_test_us_pub_VExdfbFQARn821iqP8zNaq)
|
|
24
|
+
* Environment variables are ignored to prevent accidental production key usage
|
|
25
|
+
*
|
|
26
|
+
* - PRODUCTION: Uses environment variable if set, falls back to embedded production key
|
|
27
|
+
* This allows deployment-time configuration while having safe defaults
|
|
22
28
|
*/
|
|
23
29
|
export declare function getBasisTheoryApiKey(environment?: string): string;
|
|
@@ -15,14 +15,22 @@ export const PAYMENT_CONFIGS = {
|
|
|
15
15
|
},
|
|
16
16
|
development: {
|
|
17
17
|
basisTheory: {
|
|
18
|
-
//
|
|
18
|
+
// Embedded test API key for development
|
|
19
19
|
publicApiKey: 'key_test_us_pub_VExdfbFQARn821iqP8zNaq',
|
|
20
20
|
environment: 'sandbox',
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
local: {
|
|
24
24
|
basisTheory: {
|
|
25
|
-
//
|
|
25
|
+
// Embedded test API key for local development
|
|
26
|
+
publicApiKey: 'key_test_us_pub_VExdfbFQARn821iqP8zNaq',
|
|
27
|
+
environment: 'sandbox',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
// Default fallback configuration
|
|
31
|
+
default: {
|
|
32
|
+
basisTheory: {
|
|
33
|
+
// Embedded test API key as fallback
|
|
26
34
|
publicApiKey: 'key_test_us_pub_VExdfbFQARn821iqP8zNaq',
|
|
27
35
|
environment: 'sandbox',
|
|
28
36
|
},
|
|
@@ -32,21 +40,30 @@ export const PAYMENT_CONFIGS = {
|
|
|
32
40
|
* Get payment configuration based on environment
|
|
33
41
|
*/
|
|
34
42
|
export function getPaymentConfig(environment = 'local') {
|
|
35
|
-
return PAYMENT_CONFIGS[environment] || PAYMENT_CONFIGS.local;
|
|
43
|
+
return PAYMENT_CONFIGS[environment] || PAYMENT_CONFIGS.default || PAYMENT_CONFIGS.local;
|
|
36
44
|
}
|
|
37
45
|
/**
|
|
38
46
|
* Get BasisTheory API key for current environment
|
|
39
|
-
*
|
|
47
|
+
*
|
|
48
|
+
* Behavior:
|
|
49
|
+
* - LOCAL/DEVELOPMENT: Always uses embedded test key (key_test_us_pub_VExdfbFQARn821iqP8zNaq)
|
|
50
|
+
* Environment variables are ignored to prevent accidental production key usage
|
|
51
|
+
*
|
|
52
|
+
* - PRODUCTION: Uses environment variable if set, falls back to embedded production key
|
|
53
|
+
* This allows deployment-time configuration while having safe defaults
|
|
40
54
|
*/
|
|
41
55
|
export function getBasisTheoryApiKey(environment = 'local') {
|
|
42
|
-
//
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
// For production environment, allow environment variable override
|
|
57
|
+
if (environment === 'production') {
|
|
58
|
+
if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY) {
|
|
59
|
+
return process.env.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY;
|
|
60
|
+
}
|
|
61
|
+
if (typeof process !== 'undefined' && process.env?.VITE_BASIS_THEORY_PUBLIC_API_KEY) {
|
|
62
|
+
return process.env.VITE_BASIS_THEORY_PUBLIC_API_KEY;
|
|
63
|
+
}
|
|
48
64
|
}
|
|
49
|
-
//
|
|
65
|
+
// For local/development, always use embedded test key to prevent accidental production key usage
|
|
66
|
+
// Fall back to embedded configuration based on environment
|
|
50
67
|
const config = getPaymentConfig(environment);
|
|
51
68
|
return config.basisTheory.publicApiKey;
|
|
52
69
|
}
|
|
@@ -2,6 +2,7 @@ export interface CheckoutLineItem {
|
|
|
2
2
|
externalProductId?: string | null;
|
|
3
3
|
externalVariantId?: string | null;
|
|
4
4
|
variantId?: string | null;
|
|
5
|
+
priceId?: string | null;
|
|
5
6
|
quantity: number;
|
|
6
7
|
}
|
|
7
8
|
export interface CheckoutInitParams {
|
|
@@ -229,6 +230,21 @@ export interface UseCheckoutResult {
|
|
|
229
230
|
success: boolean;
|
|
230
231
|
error?: any;
|
|
231
232
|
}>;
|
|
233
|
+
addLineItems: (lineItems: CheckoutLineItem[]) => Promise<{
|
|
234
|
+
success: boolean;
|
|
235
|
+
error?: any;
|
|
236
|
+
}>;
|
|
237
|
+
removeLineItems: (lineItems: {
|
|
238
|
+
variantId: string;
|
|
239
|
+
quantity?: number;
|
|
240
|
+
}[]) => Promise<{
|
|
241
|
+
success: boolean;
|
|
242
|
+
error?: any;
|
|
243
|
+
}>;
|
|
244
|
+
setItemQuantity: (variantId: string, quantity: number, priceId?: string) => Promise<{
|
|
245
|
+
success: boolean;
|
|
246
|
+
error?: any;
|
|
247
|
+
}>;
|
|
232
248
|
toggleOrderBump: (orderBumpOfferId: string, selected: boolean) => Promise<{
|
|
233
249
|
success: boolean;
|
|
234
250
|
error?: any;
|
|
@@ -257,6 +273,12 @@ export interface UseCheckoutResult {
|
|
|
257
273
|
shippingCountryChanged?: boolean;
|
|
258
274
|
billingCountryChanged?: boolean;
|
|
259
275
|
}>;
|
|
276
|
+
previewOrderSummary: (orderBumpOfferIds: string[], orderBumpType?: 'primary' | 'secondary' | 'vip') => Promise<{
|
|
277
|
+
savings: number;
|
|
278
|
+
savingsPct: number;
|
|
279
|
+
currency: string;
|
|
280
|
+
error?: any;
|
|
281
|
+
}>;
|
|
260
282
|
clear: () => void;
|
|
261
283
|
}
|
|
262
284
|
export declare function useCheckout(options?: UseCheckoutOptions): UseCheckoutResult;
|
|
@@ -3,7 +3,7 @@ import { useTagadaContext } from '../providers/TagadaProvider';
|
|
|
3
3
|
import { getCheckoutToken } from '../utils/urlUtils';
|
|
4
4
|
import { useCurrency } from '../hooks/useCurrency';
|
|
5
5
|
export function useCheckout(options = {}) {
|
|
6
|
-
const { apiService } = useTagadaContext();
|
|
6
|
+
const { apiService, updateCheckoutDebugData } = useTagadaContext();
|
|
7
7
|
const { code: currentCurrency } = useCurrency();
|
|
8
8
|
const [checkout, setCheckout] = useState(null);
|
|
9
9
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -21,6 +21,38 @@ export function useCheckout(options = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
}, []);
|
|
24
|
+
// Update debug data whenever checkout state changes with comprehensive information
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const debugData = checkout
|
|
27
|
+
? {
|
|
28
|
+
checkout,
|
|
29
|
+
sessionId: checkout.checkoutSession?.id,
|
|
30
|
+
checkoutToken: currentCheckoutTokenRef.current,
|
|
31
|
+
currency: checkout.summary?.currency,
|
|
32
|
+
totalAmount: checkout.summary?.totalAmount,
|
|
33
|
+
totalAdjustedAmount: checkout.summary?.totalAdjustedAmount,
|
|
34
|
+
promotionAmount: checkout.summary?.totalPromotionAmount,
|
|
35
|
+
itemsCount: checkout.summary?.items?.length || 0,
|
|
36
|
+
orderBumps: checkout.checkoutSession?.sessionLineItems?.filter((item) => item.isOrderBump) || [],
|
|
37
|
+
adjustments: checkout.summary?.adjustments || [],
|
|
38
|
+
isInitialized,
|
|
39
|
+
lastUpdated: new Date().toISOString(),
|
|
40
|
+
}
|
|
41
|
+
: null;
|
|
42
|
+
updateCheckoutDebugData(debugData, error, isLoading);
|
|
43
|
+
if (debugData) {
|
|
44
|
+
console.log('🐛 [useCheckout] Debug data updated for debug drawer', {
|
|
45
|
+
sessionId: debugData.sessionId,
|
|
46
|
+
totalAmount: debugData.totalAmount,
|
|
47
|
+
totalAdjustedAmount: debugData.totalAdjustedAmount,
|
|
48
|
+
promotionAmount: debugData.promotionAmount,
|
|
49
|
+
itemsCount: debugData.itemsCount,
|
|
50
|
+
orderBumpsCount: debugData.orderBumps?.length || 0,
|
|
51
|
+
adjustmentsCount: debugData.adjustments?.length || 0,
|
|
52
|
+
lastUpdated: debugData.lastUpdated,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}, [checkout, error, isLoading, isInitialized]); // Removed updateCheckoutDebugData from deps to prevent infinite loop
|
|
24
56
|
const init = useCallback(async (params) => {
|
|
25
57
|
// Don't allow init if we already have a checkout token
|
|
26
58
|
if (providedToken) {
|
|
@@ -85,6 +117,14 @@ export function useCheckout(options = {}) {
|
|
|
85
117
|
setCheckout(response);
|
|
86
118
|
currentCheckoutTokenRef.current = checkoutToken;
|
|
87
119
|
setIsInitialized(true);
|
|
120
|
+
console.log('📊 [useCheckout] Checkout data updated', {
|
|
121
|
+
sessionId: response.checkoutSession?.id,
|
|
122
|
+
totalAmount: response.summary?.totalAmount,
|
|
123
|
+
promotionAmount: response.summary?.totalPromotionAmount,
|
|
124
|
+
itemsCount: response.summary?.items?.length || 0,
|
|
125
|
+
orderBumpsCount: response.checkoutSession?.sessionLineItems?.filter((item) => item.isOrderBump)?.length || 0,
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
});
|
|
88
128
|
return response;
|
|
89
129
|
}
|
|
90
130
|
catch (err) {
|
|
@@ -100,7 +140,12 @@ export function useCheckout(options = {}) {
|
|
|
100
140
|
if (!currentCheckoutTokenRef.current) {
|
|
101
141
|
throw new Error('No checkout session to refresh');
|
|
102
142
|
}
|
|
143
|
+
console.log('🔄 [useCheckout] Refreshing checkout data...', {
|
|
144
|
+
checkoutToken: currentCheckoutTokenRef.current.substring(0, 8) + '...',
|
|
145
|
+
timestamp: new Date().toISOString(),
|
|
146
|
+
});
|
|
103
147
|
await getCheckout(currentCheckoutTokenRef.current);
|
|
148
|
+
console.log('✅ [useCheckout] Refresh completed, debug data will be updated automatically');
|
|
104
149
|
}, [getCheckout]);
|
|
105
150
|
const updateAddress = useCallback(async (data) => {
|
|
106
151
|
if (!checkout?.checkoutSession.id) {
|
|
@@ -213,6 +258,63 @@ export function useCheckout(options = {}) {
|
|
|
213
258
|
throw error;
|
|
214
259
|
}
|
|
215
260
|
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
261
|
+
const addLineItems = useCallback(async (lineItems) => {
|
|
262
|
+
if (!checkout?.checkoutSession.id) {
|
|
263
|
+
throw new Error('No checkout session available');
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/add`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
body: { lineItems },
|
|
269
|
+
});
|
|
270
|
+
if (response.success) {
|
|
271
|
+
await refresh();
|
|
272
|
+
}
|
|
273
|
+
return response;
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
const error = err instanceof Error ? err : new Error('Failed to add line items');
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
280
|
+
const removeLineItems = useCallback(async (lineItems) => {
|
|
281
|
+
if (!checkout?.checkoutSession.id) {
|
|
282
|
+
throw new Error('No checkout session available');
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/remove`, {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
body: { lineItems },
|
|
288
|
+
});
|
|
289
|
+
if (response.success) {
|
|
290
|
+
await refresh();
|
|
291
|
+
}
|
|
292
|
+
return response;
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
const error = err instanceof Error ? err : new Error('Failed to remove line items');
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
299
|
+
const setItemQuantity = useCallback(async (variantId, quantity, priceId) => {
|
|
300
|
+
if (!checkout?.checkoutSession.id) {
|
|
301
|
+
throw new Error('No checkout session available');
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/set-quantity`, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
body: { variantId, quantity, priceId },
|
|
307
|
+
});
|
|
308
|
+
if (response.success) {
|
|
309
|
+
await refresh();
|
|
310
|
+
}
|
|
311
|
+
return response;
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
const error = err instanceof Error ? err : new Error('Failed to set item quantity');
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
216
318
|
const toggleOrderBump = useCallback(async (orderBumpOfferId, selected) => {
|
|
217
319
|
if (!checkout?.checkoutSession.id) {
|
|
218
320
|
throw new Error('No checkout session available');
|
|
@@ -223,7 +325,13 @@ export function useCheckout(options = {}) {
|
|
|
223
325
|
body: { orderBumpOfferId, selected },
|
|
224
326
|
});
|
|
225
327
|
if (response.success) {
|
|
328
|
+
console.log('🎯 [useCheckout] Order bump toggled successfully, refreshing checkout data...', {
|
|
329
|
+
orderBumpOfferId,
|
|
330
|
+
selected,
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
});
|
|
226
333
|
await refresh();
|
|
334
|
+
console.log('✅ [useCheckout] Order bump refresh completed, debug drawer should now show updated data');
|
|
227
335
|
}
|
|
228
336
|
return response;
|
|
229
337
|
}
|
|
@@ -268,6 +376,30 @@ export function useCheckout(options = {}) {
|
|
|
268
376
|
throw error;
|
|
269
377
|
}
|
|
270
378
|
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
379
|
+
const previewOrderSummary = useCallback(async (orderBumpOfferIds, orderBumpType = 'vip') => {
|
|
380
|
+
if (!checkout?.checkoutSession.id) {
|
|
381
|
+
throw new Error('No checkout session available');
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/vip-preview`, {
|
|
385
|
+
method: 'POST',
|
|
386
|
+
body: {
|
|
387
|
+
orderBumpOfferIds,
|
|
388
|
+
orderBumpType,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
return response;
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
const error = err instanceof Error ? err : new Error('Failed to preview order summary');
|
|
395
|
+
return {
|
|
396
|
+
savings: 0,
|
|
397
|
+
savingsPct: 0,
|
|
398
|
+
currency: checkout?.summary?.currency ?? 'EUR',
|
|
399
|
+
error: error.message,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}, [apiService, checkout?.checkoutSession.id, checkout?.summary?.currency]);
|
|
271
403
|
const clear = useCallback(() => {
|
|
272
404
|
setCheckout(null);
|
|
273
405
|
setError(null);
|
|
@@ -317,9 +449,13 @@ export function useCheckout(options = {}) {
|
|
|
317
449
|
removePromotion,
|
|
318
450
|
getAppliedPromotions,
|
|
319
451
|
updateLineItems,
|
|
452
|
+
addLineItems,
|
|
453
|
+
removeLineItems,
|
|
454
|
+
setItemQuantity,
|
|
320
455
|
toggleOrderBump,
|
|
321
456
|
updateCustomer,
|
|
322
457
|
updateCustomerAndSessionInfo,
|
|
458
|
+
previewOrderSummary,
|
|
323
459
|
clear,
|
|
324
460
|
};
|
|
325
461
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface OrderBumpPreview {
|
|
2
|
+
savings: number;
|
|
3
|
+
savingsPct: number;
|
|
4
|
+
currency: string;
|
|
5
|
+
selectedOffers: {
|
|
6
|
+
productId: string | null;
|
|
7
|
+
variantId: string | null;
|
|
8
|
+
isSelected: boolean;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
export interface UseOrderBumpOptions {
|
|
12
|
+
checkoutSessionId?: string;
|
|
13
|
+
offerId: string;
|
|
14
|
+
orderBumpType?: 'primary' | 'secondary' | 'vip';
|
|
15
|
+
autoPreview?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface UseOrderBumpResult {
|
|
18
|
+
isSelected: boolean;
|
|
19
|
+
preview: OrderBumpPreview | null;
|
|
20
|
+
savings: number | null;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
isToggling: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
toggle: (selected?: boolean) => Promise<{
|
|
25
|
+
success: boolean;
|
|
26
|
+
error?: any;
|
|
27
|
+
}>;
|
|
28
|
+
refreshPreview: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare function useOrderBump(options: UseOrderBumpOptions): UseOrderBumpResult;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
|
+
export function useOrderBump(options) {
|
|
4
|
+
const { apiService } = useTagadaContext();
|
|
5
|
+
const { checkoutSessionId, offerId, orderBumpType = 'vip', autoPreview = true } = options;
|
|
6
|
+
const [isSelected, setIsSelected] = useState(false);
|
|
7
|
+
const [preview, setPreview] = useState(null);
|
|
8
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
9
|
+
const [isToggling, setIsToggling] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const refreshPreview = useCallback(async () => {
|
|
12
|
+
if (!checkoutSessionId)
|
|
13
|
+
return;
|
|
14
|
+
setIsLoading(true);
|
|
15
|
+
setError(null);
|
|
16
|
+
try {
|
|
17
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/vip-preview`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: {
|
|
20
|
+
orderBumpOfferIds: [offerId],
|
|
21
|
+
orderBumpType,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
setPreview(response);
|
|
25
|
+
// Update isSelected based on preview data
|
|
26
|
+
const offerSelected = response.selectedOffers?.some((offer) => offer.isSelected);
|
|
27
|
+
setIsSelected(offerSelected ?? false);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const error = err instanceof Error ? err : new Error('Failed to fetch preview');
|
|
31
|
+
setError(error);
|
|
32
|
+
console.error('Order bump preview failed:', error);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}, [checkoutSessionId, offerId, orderBumpType, apiService]);
|
|
38
|
+
const toggle = useCallback(async (selected) => {
|
|
39
|
+
if (!checkoutSessionId) {
|
|
40
|
+
throw new Error('No checkout session available');
|
|
41
|
+
}
|
|
42
|
+
const targetState = selected ?? !isSelected;
|
|
43
|
+
// Optimistic update
|
|
44
|
+
setIsSelected(targetState);
|
|
45
|
+
setIsToggling(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
try {
|
|
48
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/toggle-order-bump`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
body: {
|
|
51
|
+
orderBumpOfferId: offerId,
|
|
52
|
+
selected: targetState,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (response.success) {
|
|
56
|
+
// Refresh preview to get updated savings
|
|
57
|
+
await refreshPreview();
|
|
58
|
+
return { success: true };
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Revert optimistic update
|
|
62
|
+
setIsSelected(!targetState);
|
|
63
|
+
return { success: false, error: response.error };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// Revert optimistic update
|
|
68
|
+
setIsSelected(!targetState);
|
|
69
|
+
const error = err instanceof Error ? err : new Error('Failed to toggle order bump');
|
|
70
|
+
setError(error);
|
|
71
|
+
return { success: false, error: error.message };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
setIsToggling(false);
|
|
75
|
+
}
|
|
76
|
+
}, [checkoutSessionId, offerId, isSelected, apiService, refreshPreview]);
|
|
77
|
+
// Auto-fetch preview on mount and when dependencies change
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (autoPreview && checkoutSessionId) {
|
|
80
|
+
refreshPreview().catch((error) => {
|
|
81
|
+
console.error('Auto-preview failed:', error);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}, [autoPreview, refreshPreview]);
|
|
85
|
+
// Calculate current savings
|
|
86
|
+
const savings = isSelected && preview?.savings ? preview.savings : preview?.savings || null;
|
|
87
|
+
return {
|
|
88
|
+
isSelected,
|
|
89
|
+
preview,
|
|
90
|
+
savings,
|
|
91
|
+
isLoading,
|
|
92
|
+
isToggling,
|
|
93
|
+
error,
|
|
94
|
+
toggle,
|
|
95
|
+
refreshPreview,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import { useBasisTheory } from '@basis-theory/basis-theory-react';
|
|
2
3
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
4
|
import { getBasisTheoryApiKey } from '../config/payment';
|
|
4
5
|
import { usePaymentPolling } from './usePaymentPolling';
|
|
@@ -23,47 +24,25 @@ export function usePayment() {
|
|
|
23
24
|
const { createSession, startChallenge } = useThreeds();
|
|
24
25
|
// Track challenge in progress to prevent multiple challenges
|
|
25
26
|
const challengeInProgressRef = useRef(false);
|
|
26
|
-
//
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
// Stabilize environment value to prevent re-renders
|
|
28
|
+
const currentEnvironment = useMemo(() => environment?.environment || 'local', [environment?.environment]);
|
|
29
|
+
// Get API key from embedded configuration with proper environment detection
|
|
30
|
+
const apiKey = useMemo(() => getBasisTheoryApiKey(currentEnvironment), [currentEnvironment]);
|
|
31
|
+
// Initialize BasisTheory using React wrapper
|
|
32
|
+
const { bt: basisTheory, error: btError } = useBasisTheory(apiKey, {
|
|
33
|
+
elements: false,
|
|
34
|
+
});
|
|
35
|
+
// Handle BasisTheory initialization errors (only log once when state changes)
|
|
29
36
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
setError('BasisTheory API key not configured');
|
|
40
|
-
setIsBasisTheoryLoading(false);
|
|
41
|
-
}
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const { BasisTheory } = await import('@basis-theory/basis-theory-js');
|
|
45
|
-
if (isMounted) {
|
|
46
|
-
const bt = await new BasisTheory().init(apiKey, {
|
|
47
|
-
elements: false,
|
|
48
|
-
});
|
|
49
|
-
setBasisTheory(bt);
|
|
50
|
-
setIsBasisTheoryLoading(false);
|
|
51
|
-
console.log('✅ BasisTheory initialized successfully');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
console.error('Failed to load BasisTheory:', err);
|
|
56
|
-
if (isMounted) {
|
|
57
|
-
setError('Failed to initialize payment processor');
|
|
58
|
-
setIsBasisTheoryLoading(false);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
void loadBasisTheory();
|
|
63
|
-
return () => {
|
|
64
|
-
isMounted = false;
|
|
65
|
-
};
|
|
66
|
-
}, [environment]);
|
|
37
|
+
if (btError) {
|
|
38
|
+
console.error('BasisTheory initialization error:', btError);
|
|
39
|
+
setError('Failed to initialize payment processor: ' + btError.message);
|
|
40
|
+
}
|
|
41
|
+
else if (basisTheory && !error) {
|
|
42
|
+
console.log('✅ BasisTheory initialized successfully');
|
|
43
|
+
setError(null); // Clear any previous errors
|
|
44
|
+
}
|
|
45
|
+
}, [basisTheory, btError]); // Removed error from dependency to prevent loops
|
|
67
46
|
// Clean up polling when component unmounts
|
|
68
47
|
useEffect(() => {
|
|
69
48
|
return () => {
|
|
@@ -154,9 +133,6 @@ export function usePayment() {
|
|
|
154
133
|
}, [apiService, startChallenge, startPolling]);
|
|
155
134
|
// Create card payment instrument
|
|
156
135
|
const createCardPaymentInstrument = useCallback(async (cardData) => {
|
|
157
|
-
if (isBasisTheoryLoading) {
|
|
158
|
-
throw new Error('Payment processor is still initializing. Please wait...');
|
|
159
|
-
}
|
|
160
136
|
if (!basisTheory) {
|
|
161
137
|
throw new Error('Payment processor not initialized');
|
|
162
138
|
}
|
|
@@ -195,11 +171,11 @@ export function usePayment() {
|
|
|
195
171
|
console.error('Error creating card payment instrument:', error);
|
|
196
172
|
throw error;
|
|
197
173
|
}
|
|
198
|
-
}, [basisTheory, apiService
|
|
174
|
+
}, [basisTheory, apiService]);
|
|
199
175
|
// Create Apple Pay payment instrument
|
|
200
176
|
const createApplePayPaymentInstrument = useCallback(async (applePayToken) => {
|
|
201
|
-
if (
|
|
202
|
-
throw new Error('Payment processor
|
|
177
|
+
if (!basisTheory) {
|
|
178
|
+
throw new Error('Payment processor not initialized');
|
|
203
179
|
}
|
|
204
180
|
if (!applePayToken.id) {
|
|
205
181
|
throw new Error('Apple Pay token is missing');
|
|
@@ -227,7 +203,7 @@ export function usePayment() {
|
|
|
227
203
|
console.error('Error creating Apple Pay payment instrument:', error);
|
|
228
204
|
throw error;
|
|
229
205
|
}
|
|
230
|
-
}, [
|
|
206
|
+
}, [basisTheory, apiService]);
|
|
231
207
|
// Process payment directly with checkout session
|
|
232
208
|
const processPaymentDirect = useCallback(async (checkoutSessionId, paymentInstrumentId, threedsSessionId, options = {}) => {
|
|
233
209
|
try {
|
|
@@ -349,7 +325,7 @@ export function usePayment() {
|
|
|
349
325
|
processPaymentWithInstrument,
|
|
350
326
|
createCardPaymentInstrument,
|
|
351
327
|
createApplePayPaymentInstrument,
|
|
352
|
-
isLoading: isLoading ||
|
|
328
|
+
isLoading: isLoading || !basisTheory, // Indicate loading if BasisTheory is not initialized
|
|
353
329
|
error,
|
|
354
330
|
clearError,
|
|
355
331
|
currentPaymentId,
|
|
@@ -7,16 +7,12 @@ export interface PaymentInstrument {
|
|
|
7
7
|
maskedCardNumber?: string;
|
|
8
8
|
expirationMonth?: number;
|
|
9
9
|
expirationYear?: number;
|
|
10
|
-
|
|
11
|
-
bin?: string;
|
|
12
|
-
last4?: string;
|
|
13
|
-
} | null;
|
|
10
|
+
};
|
|
14
11
|
}
|
|
15
12
|
export interface ThreedsSession {
|
|
16
13
|
id: string;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
sessionData: any;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
provider: string;
|
|
20
16
|
}
|
|
21
17
|
export interface ThreedsChallenge {
|
|
22
18
|
sessionId: string;
|
|
@@ -33,6 +29,8 @@ export interface ThreedsHook {
|
|
|
33
29
|
isLoading: boolean;
|
|
34
30
|
error: Error | null;
|
|
35
31
|
}
|
|
36
|
-
|
|
32
|
+
interface UseThreedsOptions {
|
|
37
33
|
defaultProvider?: ThreedsProvider;
|
|
38
|
-
}
|
|
34
|
+
}
|
|
35
|
+
export declare function useThreeds(options?: UseThreedsOptions): ThreedsHook;
|
|
36
|
+
export {};
|
|
@@ -4,22 +4,17 @@ import { getBasisTheoryApiKey } from '../config/payment';
|
|
|
4
4
|
import { useThreedsModal } from './useThreedsModal';
|
|
5
5
|
export function useThreeds(options = {}) {
|
|
6
6
|
const { defaultProvider = 'basis_theory' } = options;
|
|
7
|
-
const { apiService
|
|
7
|
+
const { apiService } = useTagadaContext();
|
|
8
8
|
const [isLoading, setIsLoading] = useState(false);
|
|
9
9
|
const [error, setError] = useState(null);
|
|
10
10
|
const { createThreedsModal, closeThreedsModal } = useThreedsModal();
|
|
11
11
|
const [basisTheory3ds, setBasisTheory3ds] = useState(null);
|
|
12
|
+
const [currentChallenge, setCurrentChallenge] = useState(null);
|
|
12
13
|
// Dynamically import BasisTheory3ds on the client side only
|
|
13
14
|
useEffect(() => {
|
|
14
15
|
let isMounted = true;
|
|
15
16
|
const loadBasisTheory = async () => {
|
|
16
17
|
try {
|
|
17
|
-
// Get API key from embedded configuration (with env override support)
|
|
18
|
-
const apiKey = getBasisTheoryApiKey(environment?.environment || 'local');
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
console.warn('BasisTheory API key not configured');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
18
|
// Dynamically import the library only on the client side
|
|
24
19
|
const { BasisTheory3ds } = await import('@basis-theory/web-threeds');
|
|
25
20
|
if (isMounted) {
|
|
@@ -38,20 +33,17 @@ export function useThreeds(options = {}) {
|
|
|
38
33
|
return () => {
|
|
39
34
|
isMounted = false;
|
|
40
35
|
};
|
|
41
|
-
}, [
|
|
36
|
+
}, []);
|
|
42
37
|
// Create a 3DS session with BasisTheory
|
|
43
38
|
const createBasisTheorySession = useCallback(async (paymentInstrument) => {
|
|
44
39
|
try {
|
|
45
40
|
if (!basisTheory3ds) {
|
|
46
41
|
throw new Error('BasisTheory3ds not loaded yet');
|
|
47
42
|
}
|
|
48
|
-
//
|
|
49
|
-
const apiKey = getBasisTheoryApiKey(
|
|
50
|
-
if (!apiKey) {
|
|
51
|
-
throw new Error('BasisTheory API key not configured');
|
|
52
|
-
}
|
|
43
|
+
// Use the same API key approach as the working CMS version
|
|
44
|
+
const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
|
|
53
45
|
const bt3ds = basisTheory3ds(apiKey);
|
|
54
|
-
console.log('
|
|
46
|
+
console.log('paymentInstrument paymentInstrument', paymentInstrument?.token);
|
|
55
47
|
const session = await bt3ds.createSession({
|
|
56
48
|
tokenId: paymentInstrument.token,
|
|
57
49
|
});
|
|
@@ -70,7 +62,7 @@ export function useThreeds(options = {}) {
|
|
|
70
62
|
console.error('Error creating BasisTheory 3DS session:', error);
|
|
71
63
|
throw error;
|
|
72
64
|
}
|
|
73
|
-
}, [apiService, basisTheory3ds
|
|
65
|
+
}, [apiService, basisTheory3ds]);
|
|
74
66
|
// Generic createSession method that supports multiple providers
|
|
75
67
|
const createSession = useCallback(async (paymentInstrument, options) => {
|
|
76
68
|
const provider = options?.provider || defaultProvider;
|
|
@@ -96,31 +88,37 @@ export function useThreeds(options = {}) {
|
|
|
96
88
|
}
|
|
97
89
|
}, [defaultProvider, createBasisTheorySession]);
|
|
98
90
|
// Start a 3DS challenge with BasisTheory
|
|
99
|
-
const startBasisTheoryChallenge = useCallback(async (
|
|
91
|
+
const startBasisTheoryChallenge = useCallback(async (sessionId, acsChallengeUrl, acsTransactionId, threeDSVersion) => {
|
|
100
92
|
try {
|
|
101
93
|
if (!basisTheory3ds) {
|
|
102
94
|
throw new Error('BasisTheory3ds not loaded yet');
|
|
103
95
|
}
|
|
104
|
-
//
|
|
105
|
-
const apiKey = getBasisTheoryApiKey(
|
|
96
|
+
// Use the same API key approach as the working CMS version
|
|
97
|
+
const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
|
|
106
98
|
if (!apiKey) {
|
|
107
|
-
throw new Error('BasisTheory API key is not
|
|
99
|
+
throw new Error('BasisTheory API key is not set');
|
|
108
100
|
}
|
|
109
101
|
const modal = createThreedsModal({
|
|
110
102
|
onClose: () => {
|
|
103
|
+
// Throw error when user closes the modal
|
|
111
104
|
throw new Error('Authentication was cancelled by the user');
|
|
112
105
|
},
|
|
113
106
|
});
|
|
114
107
|
const bt3ds = basisTheory3ds(apiKey);
|
|
115
|
-
console.log('
|
|
108
|
+
console.log('bt3ds starting challenge with params', {
|
|
109
|
+
sessionId,
|
|
110
|
+
acsChallengeUrl,
|
|
111
|
+
acsTransactionId,
|
|
112
|
+
threeDSVersion,
|
|
113
|
+
});
|
|
116
114
|
const challengeCompletion = await bt3ds.startChallenge({
|
|
117
|
-
sessionId
|
|
118
|
-
acsChallengeUrl
|
|
119
|
-
acsTransactionId
|
|
120
|
-
threeDSVersion:
|
|
115
|
+
sessionId,
|
|
116
|
+
acsChallengeUrl,
|
|
117
|
+
acsTransactionId,
|
|
118
|
+
threeDSVersion: threeDSVersion,
|
|
121
119
|
containerId: modal.containerId + '-content',
|
|
122
120
|
mode: 'iframe',
|
|
123
|
-
timeout: 60000 * 3,
|
|
121
|
+
timeout: 60000 * 3,
|
|
124
122
|
});
|
|
125
123
|
closeThreedsModal();
|
|
126
124
|
return challengeCompletion;
|
|
@@ -130,7 +128,7 @@ export function useThreeds(options = {}) {
|
|
|
130
128
|
closeThreedsModal();
|
|
131
129
|
throw error;
|
|
132
130
|
}
|
|
133
|
-
}, [basisTheory3ds, createThreedsModal, closeThreedsModal
|
|
131
|
+
}, [basisTheory3ds, createThreedsModal, closeThreedsModal]);
|
|
134
132
|
// Generic startChallenge method that supports multiple providers
|
|
135
133
|
const startChallenge = useCallback(async (challengeData, options) => {
|
|
136
134
|
const provider = options?.provider || defaultProvider;
|
|
@@ -138,7 +136,7 @@ export function useThreeds(options = {}) {
|
|
|
138
136
|
setError(null);
|
|
139
137
|
try {
|
|
140
138
|
if (provider === 'basis_theory') {
|
|
141
|
-
return await startBasisTheoryChallenge(challengeData);
|
|
139
|
+
return await startBasisTheoryChallenge(challengeData.sessionId, challengeData.acsChallengeUrl, challengeData.acsTransactionId, challengeData.threeDSVersion);
|
|
142
140
|
}
|
|
143
141
|
else {
|
|
144
142
|
throw new Error(`Unsupported 3DS provider: ${String(provider)}`);
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
export interface ThreedsModalHook {
|
|
13
|
-
createThreedsModal: (options?: ThreedsModalOptions) => ThreedsModalInstance;
|
|
1
|
+
export declare function useThreedsModal(): {
|
|
2
|
+
createThreedsModal: (options?: {
|
|
3
|
+
containerId?: string;
|
|
4
|
+
mode?: "fixed" | "auto-fit";
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
}) => {
|
|
7
|
+
containerId: string;
|
|
8
|
+
getContentElement: () => HTMLElement | null;
|
|
9
|
+
updateModalSize: () => void;
|
|
10
|
+
cleanup: () => void;
|
|
11
|
+
};
|
|
14
12
|
closeThreedsModal: (containerId?: string) => void;
|
|
15
|
-
}
|
|
16
|
-
export declare function useThreedsModal(): ThreedsModalHook;
|
|
13
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { useCallback } from 'react';
|
|
2
3
|
export function useThreedsModal() {
|
|
3
4
|
// Close the 3DS modal
|
|
@@ -44,9 +45,9 @@ export function useThreedsModal() {
|
|
|
44
45
|
zIndex: '9998',
|
|
45
46
|
display: 'flex',
|
|
46
47
|
justifyContent: 'center',
|
|
47
|
-
alignItems: 'center',
|
|
48
|
+
alignItems: 'center', // Default to center alignment
|
|
48
49
|
transition: 'opacity 0.3s ease',
|
|
49
|
-
opacity: '0',
|
|
50
|
+
opacity: '0', // Start transparent
|
|
50
51
|
});
|
|
51
52
|
// Create modal container
|
|
52
53
|
const container = document.createElement('div');
|
|
@@ -57,7 +58,7 @@ export function useThreedsModal() {
|
|
|
57
58
|
const baseStyles = {
|
|
58
59
|
position: 'relative',
|
|
59
60
|
backgroundColor: 'white',
|
|
60
|
-
borderRadius: '12px',
|
|
61
|
+
borderRadius: '12px', // Default border radius
|
|
61
62
|
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.2)',
|
|
62
63
|
maxWidth: '100%',
|
|
63
64
|
overflow: 'hidden',
|
|
@@ -69,10 +70,12 @@ export function useThreedsModal() {
|
|
|
69
70
|
// Function to apply styles based on screen size and mode
|
|
70
71
|
const applyResponsiveStyles = () => {
|
|
71
72
|
isMobile = window.innerWidth < 768;
|
|
73
|
+
// Always ensure backdrop is properly centered
|
|
72
74
|
backdrop.style.alignItems = 'center';
|
|
73
75
|
backdrop.style.justifyContent = 'center';
|
|
74
76
|
if (mode === 'fixed') {
|
|
75
77
|
if (isMobile) {
|
|
78
|
+
// Full screen modal for mobile
|
|
76
79
|
Object.assign(container.style, {
|
|
77
80
|
...baseStyles,
|
|
78
81
|
position: 'fixed',
|
|
@@ -82,7 +85,7 @@ export function useThreedsModal() {
|
|
|
82
85
|
bottom: '0',
|
|
83
86
|
width: '100%',
|
|
84
87
|
height: '100%',
|
|
85
|
-
borderRadius: '0',
|
|
88
|
+
borderRadius: '0', // No border radius for full screen
|
|
86
89
|
margin: '0',
|
|
87
90
|
backgroundColor: 'white',
|
|
88
91
|
transform: 'scale(0.95)',
|
|
@@ -91,6 +94,7 @@ export function useThreedsModal() {
|
|
|
91
94
|
});
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
97
|
+
// Desktop styles
|
|
94
98
|
Object.assign(container.style, {
|
|
95
99
|
...baseStyles,
|
|
96
100
|
width: '550px',
|
|
@@ -108,6 +112,7 @@ export function useThreedsModal() {
|
|
|
108
112
|
else {
|
|
109
113
|
// auto-fit mode
|
|
110
114
|
if (isMobile) {
|
|
115
|
+
// Full screen modal for mobile
|
|
111
116
|
Object.assign(container.style, {
|
|
112
117
|
...baseStyles,
|
|
113
118
|
position: 'fixed',
|
|
@@ -117,7 +122,7 @@ export function useThreedsModal() {
|
|
|
117
122
|
bottom: '0',
|
|
118
123
|
width: '100%',
|
|
119
124
|
height: '100%',
|
|
120
|
-
borderRadius: '0',
|
|
125
|
+
borderRadius: '0', // No border radius for full screen
|
|
121
126
|
margin: '0',
|
|
122
127
|
backgroundColor: 'white',
|
|
123
128
|
transform: 'scale(0.95)',
|
|
@@ -126,6 +131,7 @@ export function useThreedsModal() {
|
|
|
126
131
|
});
|
|
127
132
|
}
|
|
128
133
|
else {
|
|
134
|
+
// Desktop styles
|
|
129
135
|
Object.assign(container.style, {
|
|
130
136
|
...baseStyles,
|
|
131
137
|
width: '550px',
|
|
@@ -249,6 +255,7 @@ export function useThreedsModal() {
|
|
|
249
255
|
if (!content)
|
|
250
256
|
return;
|
|
251
257
|
if (isMobile) {
|
|
258
|
+
// On mobile, we're using full screen so just ensure content scrolls
|
|
252
259
|
content.style.height = 'calc(100% - 60px)';
|
|
253
260
|
content.style.overflowY = 'auto';
|
|
254
261
|
}
|
|
@@ -258,10 +265,12 @@ export function useThreedsModal() {
|
|
|
258
265
|
const headerHeight = header.offsetHeight;
|
|
259
266
|
const maxModalHeight = viewportHeight * 0.85;
|
|
260
267
|
if (contentHeight + headerHeight < maxModalHeight) {
|
|
268
|
+
// Content fits, let it determine the height
|
|
261
269
|
container.style.height = 'auto';
|
|
262
270
|
content.style.overflowY = 'visible';
|
|
263
271
|
}
|
|
264
272
|
else {
|
|
273
|
+
// Content is too large, cap the height
|
|
265
274
|
container.style.height = `${maxModalHeight}px`;
|
|
266
275
|
content.style.height = `${maxModalHeight - headerHeight}px`;
|
|
267
276
|
content.style.overflowY = 'auto';
|
|
@@ -270,27 +279,33 @@ export function useThreedsModal() {
|
|
|
270
279
|
};
|
|
271
280
|
// Handle window resize
|
|
272
281
|
const handleResize = () => {
|
|
282
|
+
// Store current opacity values before applying new styles
|
|
273
283
|
const currentOpacity = container.style.opacity;
|
|
274
284
|
const currentTransform = container.style.transform;
|
|
275
285
|
applyResponsiveStyles();
|
|
276
286
|
updateContentStyles();
|
|
287
|
+
// Restore opacity and transform to maintain visibility
|
|
277
288
|
container.style.opacity = currentOpacity;
|
|
278
289
|
container.style.transform = currentTransform;
|
|
290
|
+
// For auto-fit mode, also adjust the height
|
|
279
291
|
if (mode === 'auto-fit') {
|
|
280
292
|
adjustAutoFitHeight();
|
|
281
293
|
}
|
|
282
294
|
};
|
|
283
295
|
// Set up mutation observer to detect content changes
|
|
284
|
-
const contentObserver = new MutationObserver(() => {
|
|
296
|
+
const contentObserver = new MutationObserver((mutations) => {
|
|
297
|
+
// When content changes, adjust the height
|
|
285
298
|
if (mode === 'auto-fit') {
|
|
299
|
+
// Small delay to ensure content has rendered
|
|
286
300
|
setTimeout(adjustAutoFitHeight, 50);
|
|
287
301
|
}
|
|
288
302
|
});
|
|
303
|
+
// Configure and start the mutation observer
|
|
289
304
|
contentObserver.observe(content, {
|
|
290
|
-
childList: true,
|
|
291
|
-
subtree: true,
|
|
292
|
-
characterData: true,
|
|
293
|
-
attributes: true,
|
|
305
|
+
childList: true, // Watch for changes to child elements
|
|
306
|
+
subtree: true, // Watch the entire subtree
|
|
307
|
+
characterData: true, // Watch for changes to text content
|
|
308
|
+
attributes: true, // Watch for changes to attributes
|
|
294
309
|
});
|
|
295
310
|
// For auto-fit mode, set up resize observer for content element
|
|
296
311
|
let resizeObserver = null;
|
|
@@ -311,7 +326,7 @@ export function useThreedsModal() {
|
|
|
311
326
|
return {
|
|
312
327
|
containerId,
|
|
313
328
|
getContentElement: () => document.getElementById(`${containerId}-content`),
|
|
314
|
-
updateModalSize,
|
|
329
|
+
updateModalSize, // Expose function to manually update size
|
|
315
330
|
cleanup: () => {
|
|
316
331
|
window.removeEventListener('resize', handleResize);
|
|
317
332
|
if (resizeObserver) {
|
package/dist/react/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { TagadaProvider } from './providers/TagadaProvider';
|
|
|
5
5
|
export { useCheckout } from './hooks/useCheckout';
|
|
6
6
|
export { useProducts } from './hooks/useProducts';
|
|
7
7
|
export { useOffers } from './hooks/useOffers';
|
|
8
|
+
export { useOrderBump } from './hooks/useOrderBump';
|
|
8
9
|
export { useSession } from './hooks/useSession';
|
|
9
10
|
export { useCurrency } from './hooks/useCurrency';
|
|
10
11
|
export { useCustomer } from './hooks/useCustomer';
|
|
@@ -19,8 +20,8 @@ export { useThreeds } from './hooks/useThreeds';
|
|
|
19
20
|
export { useThreedsModal } from './hooks/useThreedsModal';
|
|
20
21
|
export type { Customer, Session, AuthState, Locale, Currency, Store, Environment, EnvironmentConfig, Order, OrderItem, OrderSummary, OrderAddress, PickupPoint, } from './types';
|
|
21
22
|
export type { UseCheckoutOptions, UseCheckoutResult, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutData, Promotion, } from './hooks/useCheckout';
|
|
23
|
+
export type { UseOrderBumpOptions, UseOrderBumpResult, OrderBumpPreview } from './hooks/useOrderBump';
|
|
22
24
|
export type { Payment, PollingOptions, PaymentPollingHook } from './hooks/usePaymentPolling';
|
|
23
25
|
export type { PaymentInstrument, ThreedsSession, ThreedsChallenge, ThreedsOptions, ThreedsHook, ThreedsProvider, } from './hooks/useThreeds';
|
|
24
|
-
export type { ThreedsModalOptions, ThreedsModalInstance, ThreedsModalHook } from './hooks/useThreedsModal';
|
|
25
26
|
export type { ApplePayToken, CardPaymentMethod, PaymentResponse, PaymentOptions, PaymentInstrumentResponse, PaymentHook, } from './hooks/usePayment';
|
|
26
27
|
export { formatMoney, formatMoneyWithoutSymbol, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, convertCurrency, formatSimpleMoney, } from './utils/money';
|
package/dist/react/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { TagadaProvider } from './providers/TagadaProvider';
|
|
|
8
8
|
export { useCheckout } from './hooks/useCheckout';
|
|
9
9
|
export { useProducts } from './hooks/useProducts';
|
|
10
10
|
export { useOffers } from './hooks/useOffers';
|
|
11
|
+
export { useOrderBump } from './hooks/useOrderBump';
|
|
11
12
|
export { useSession } from './hooks/useSession';
|
|
12
13
|
export { useCurrency } from './hooks/useCurrency';
|
|
13
14
|
export { useCustomer } from './hooks/useCustomer';
|
|
@@ -39,17 +39,11 @@ interface TagadaProviderProps {
|
|
|
39
39
|
children: ReactNode;
|
|
40
40
|
environment?: Environment;
|
|
41
41
|
customApiConfig?: Partial<EnvironmentConfig>;
|
|
42
|
-
developmentMode?: boolean;
|
|
43
42
|
debugMode?: boolean;
|
|
44
43
|
storeId?: string;
|
|
45
44
|
accountId?: string;
|
|
46
|
-
mockData?: {
|
|
47
|
-
customer?: Partial<Customer>;
|
|
48
|
-
session?: Partial<Session>;
|
|
49
|
-
store?: Partial<Store>;
|
|
50
|
-
};
|
|
51
45
|
}
|
|
52
|
-
export declare function TagadaProvider({ children, environment, customApiConfig,
|
|
53
|
-
storeId, accountId,
|
|
46
|
+
export declare function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
|
|
47
|
+
storeId, accountId, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
54
48
|
export declare function useTagadaContext(): TagadaContextValue;
|
|
55
49
|
export {};
|
|
@@ -29,8 +29,8 @@ const InitializationLoader = () => (_jsx("div", { style: {
|
|
|
29
29
|
}
|
|
30
30
|
` }) }));
|
|
31
31
|
const TagadaContext = createContext(null);
|
|
32
|
-
export function TagadaProvider({ children, environment, customApiConfig,
|
|
33
|
-
storeId, accountId,
|
|
32
|
+
export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
|
|
33
|
+
storeId, accountId, }) {
|
|
34
34
|
const [isLoading, setIsLoading] = useState(true);
|
|
35
35
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
36
36
|
const [token, setToken] = useState(null);
|
|
@@ -284,49 +284,6 @@ storeId, accountId, mockData, }) {
|
|
|
284
284
|
try {
|
|
285
285
|
console.debug('[SDK] Initializing token...');
|
|
286
286
|
setIsLoading(true);
|
|
287
|
-
if (developmentMode) {
|
|
288
|
-
console.debug('[SDK] Development mode: Using mock data');
|
|
289
|
-
// Use mock data in development mode
|
|
290
|
-
if (mockData?.customer) {
|
|
291
|
-
setCustomer({
|
|
292
|
-
id: 'dev-customer-123',
|
|
293
|
-
email: 'dev@example.com',
|
|
294
|
-
firstName: 'John',
|
|
295
|
-
lastName: 'Doe',
|
|
296
|
-
phone: '+1234567890',
|
|
297
|
-
isAuthenticated: true,
|
|
298
|
-
role: 'authenticated',
|
|
299
|
-
...mockData.customer,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
if (mockData?.session) {
|
|
303
|
-
setSession({
|
|
304
|
-
sessionId: 'dev-session-123',
|
|
305
|
-
storeId: 'dev-store-123',
|
|
306
|
-
accountId: 'dev-account-123',
|
|
307
|
-
customerId: 'dev-customer-123',
|
|
308
|
-
role: 'authenticated',
|
|
309
|
-
isValid: true,
|
|
310
|
-
isLoading: false,
|
|
311
|
-
...mockData.session,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
if (mockData?.store) {
|
|
315
|
-
setStore({
|
|
316
|
-
id: 'dev-store-123',
|
|
317
|
-
name: 'Development Store',
|
|
318
|
-
domain: 'dev.localhost',
|
|
319
|
-
currency: 'USD',
|
|
320
|
-
locale: 'en-US',
|
|
321
|
-
presentmentCurrencies: ['USD'],
|
|
322
|
-
chargeCurrencies: ['USD'],
|
|
323
|
-
...mockData.store,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
setIsInitialized(true);
|
|
327
|
-
setIsLoading(false);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
287
|
// Check for existing token
|
|
331
288
|
const existingToken = getClientToken();
|
|
332
289
|
let tokenToUse = null;
|
|
@@ -374,7 +331,7 @@ storeId, accountId, mockData, }) {
|
|
|
374
331
|
}
|
|
375
332
|
};
|
|
376
333
|
void initializeToken();
|
|
377
|
-
}, [
|
|
334
|
+
}, [storeId, createAnonymousToken, initializeSession]);
|
|
378
335
|
// Update auth state when customer/session changes
|
|
379
336
|
useEffect(() => {
|
|
380
337
|
setAuth({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Modern React SDK for building Tagada Pay plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@basis-theory/apple-pay-js": "^2.0.2",
|
|
37
|
-
"@basis-theory/basis-theory-js": "^4.
|
|
38
|
-
"@basis-theory/basis-theory-react": "^1.32.
|
|
37
|
+
"@basis-theory/basis-theory-js": "^4.30.0",
|
|
38
|
+
"@basis-theory/basis-theory-react": "^1.32.5",
|
|
39
39
|
"@basis-theory/web-threeds": "^1.0.1",
|
|
40
40
|
"axios": "^1.6.0",
|
|
41
41
|
"react-intl": "^7.1.11"
|