@tagadapay/plugin-sdk 1.0.9 → 1.0.10
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 +16 -0
- package/dist/react/hooks/useCheckout.js +60 -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 +0 -1
- 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;
|
|
@@ -213,6 +213,63 @@ export function useCheckout(options = {}) {
|
|
|
213
213
|
throw error;
|
|
214
214
|
}
|
|
215
215
|
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
216
|
+
const addLineItems = useCallback(async (lineItems) => {
|
|
217
|
+
if (!checkout?.checkoutSession.id) {
|
|
218
|
+
throw new Error('No checkout session available');
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/add`, {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
body: { lineItems },
|
|
224
|
+
});
|
|
225
|
+
if (response.success) {
|
|
226
|
+
await refresh();
|
|
227
|
+
}
|
|
228
|
+
return response;
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
const error = err instanceof Error ? err : new Error('Failed to add line items');
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
235
|
+
const removeLineItems = useCallback(async (lineItems) => {
|
|
236
|
+
if (!checkout?.checkoutSession.id) {
|
|
237
|
+
throw new Error('No checkout session available');
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/remove`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
body: { lineItems },
|
|
243
|
+
});
|
|
244
|
+
if (response.success) {
|
|
245
|
+
await refresh();
|
|
246
|
+
}
|
|
247
|
+
return response;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const error = err instanceof Error ? err : new Error('Failed to remove line items');
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
254
|
+
const setItemQuantity = useCallback(async (variantId, quantity, priceId) => {
|
|
255
|
+
if (!checkout?.checkoutSession.id) {
|
|
256
|
+
throw new Error('No checkout session available');
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/line-items/set-quantity`, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
body: { variantId, quantity, priceId },
|
|
262
|
+
});
|
|
263
|
+
if (response.success) {
|
|
264
|
+
await refresh();
|
|
265
|
+
}
|
|
266
|
+
return response;
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
const error = err instanceof Error ? err : new Error('Failed to set item quantity');
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}, [apiService, checkout?.checkoutSession.id, refresh]);
|
|
216
273
|
const toggleOrderBump = useCallback(async (orderBumpOfferId, selected) => {
|
|
217
274
|
if (!checkout?.checkoutSession.id) {
|
|
218
275
|
throw new Error('No checkout session available');
|
|
@@ -317,6 +374,9 @@ export function useCheckout(options = {}) {
|
|
|
317
374
|
removePromotion,
|
|
318
375
|
getAppliedPromotions,
|
|
319
376
|
updateLineItems,
|
|
377
|
+
addLineItems,
|
|
378
|
+
removeLineItems,
|
|
379
|
+
setItemQuantity,
|
|
320
380
|
toggleOrderBump,
|
|
321
381
|
updateCustomer,
|
|
322
382
|
updateCustomerAndSessionInfo,
|
|
@@ -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
|
@@ -21,6 +21,5 @@ export type { Customer, Session, AuthState, Locale, Currency, Store, Environment
|
|
|
21
21
|
export type { UseCheckoutOptions, UseCheckoutResult, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutData, Promotion, } from './hooks/useCheckout';
|
|
22
22
|
export type { Payment, PollingOptions, PaymentPollingHook } from './hooks/usePaymentPolling';
|
|
23
23
|
export type { PaymentInstrument, ThreedsSession, ThreedsChallenge, ThreedsOptions, ThreedsHook, ThreedsProvider, } from './hooks/useThreeds';
|
|
24
|
-
export type { ThreedsModalOptions, ThreedsModalInstance, ThreedsModalHook } from './hooks/useThreedsModal';
|
|
25
24
|
export type { ApplePayToken, CardPaymentMethod, PaymentResponse, PaymentOptions, PaymentInstrumentResponse, PaymentHook, } from './hooks/usePayment';
|
|
26
25
|
export { formatMoney, formatMoneyWithoutSymbol, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, convertCurrency, formatSimpleMoney, } from './utils/money';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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"
|