@tagadapay/plugin-sdk 3.1.5 → 3.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1129 -1129
- package/build-cdn.js +220 -113
- package/dist/external-tracker.js +1225 -558
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/hooks/useApplePay.js +25 -36
- package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/react/utils/money.d.ts +4 -3
- package/dist/react/utils/money.js +39 -6
- package/dist/react/utils/trackingUtils.js +1 -0
- package/dist/tagada-sdk.js +10142 -0
- package/dist/tagada-sdk.min.js +43 -0
- package/dist/tagada-sdk.min.js.map +7 -0
- package/dist/v2/core/client.js +34 -2
- package/dist/v2/core/config/environment.js +9 -2
- package/dist/v2/core/funnelClient.d.ts +180 -2
- package/dist/v2/core/funnelClient.js +289 -6
- package/dist/v2/core/resources/apiClient.js +1 -1
- package/dist/v2/core/resources/checkout.d.ts +68 -0
- package/dist/v2/core/resources/funnel.d.ts +25 -0
- package/dist/v2/core/resources/payments.d.ts +70 -3
- package/dist/v2/core/resources/payments.js +72 -7
- package/dist/v2/core/utils/index.d.ts +1 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
- package/dist/v2/core/utils/pluginConfig.js +68 -5
- package/dist/v2/core/utils/previewMode.d.ts +7 -0
- package/dist/v2/core/utils/previewMode.js +72 -14
- package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
- package/dist/v2/core/utils/previewModeIndicator.js +414 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
- package/dist/v2/core/utils/tokenStorage.js +15 -1
- package/dist/v2/index.d.ts +9 -3
- package/dist/v2/index.js +8 -3
- package/dist/v2/react/components/ApplePayButton.d.ts +22 -123
- package/dist/v2/react/components/ApplePayButton.js +247 -317
- package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
- package/dist/v2/react/components/FunnelScriptInjector.js +255 -162
- package/dist/v2/react/components/GooglePayButton.d.ts +2 -0
- package/dist/v2/react/components/GooglePayButton.js +80 -64
- package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
- package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
- package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
- package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +48 -6
- package/dist/v2/react/hooks/useFunnel.js +25 -5
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +10 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +48 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.d.ts +21 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.js +198 -0
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +15 -3
- package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +34 -2
- package/dist/v2/react/hooks/usePaymentQuery.js +731 -7
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
- package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
- package/dist/v2/react/hooks/usePixelTracking.d.ts +56 -0
- package/dist/v2/react/hooks/usePixelTracking.js +508 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +64 -0
- package/dist/v2/react/hooks/useStepConfig.js +53 -0
- package/dist/v2/react/index.d.ts +15 -5
- package/dist/v2/react/index.js +8 -2
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +41 -13
- package/dist/v2/react/providers/TagadaProvider.js +24 -23
- package/dist/v2/standalone/external-tracker.d.ts +2 -0
- package/dist/v2/standalone/external-tracker.js +6 -3
- package/package.json +112 -112
- package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
- package/dist/v2/react/hooks/useApplePay.js +0 -247
|
@@ -4,13 +4,32 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* Uses v2 useExpressPaymentMethods hook and follows clean architecture principles
|
|
5
5
|
*/
|
|
6
6
|
import GooglePayButtonReact from '@google-pay/button-react';
|
|
7
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
8
8
|
import { useExpressPaymentMethods } from '../hooks/useExpressPaymentMethods';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const GooglePayButton = ({ className = '', disabled = false, onSuccess, onError, onCancel, checkout, size = 'lg', buttonColor = 'black', buttonType = 'plain', }) => {
|
|
9
|
+
import { usePaymentQuery } from '../hooks/usePaymentQuery';
|
|
10
|
+
import { useShippingRatesQuery } from '../hooks/useShippingRatesQuery';
|
|
11
|
+
import { getBasisTheoryKeys } from '../../../config/basisTheory';
|
|
12
|
+
export const GooglePayButton = ({ className = '', disabled = false, onSuccess, onError, onCancel, checkout, size = 'lg', buttonColor = 'black', buttonType = 'plain', requiresShipping: requiresShippingProp, }) => {
|
|
13
13
|
const { googlePayPaymentMethod, shippingMethods, lineItems, handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail, reComputeOrderSummary, setError: setContextError, } = useExpressPaymentMethods();
|
|
14
|
+
// Use payment query hook for processing
|
|
15
|
+
const { processGooglePayPayment } = usePaymentQuery();
|
|
16
|
+
// Use shipping rates hook for selecting shipping rates
|
|
17
|
+
const { selectRate } = useShippingRatesQuery({ checkout });
|
|
18
|
+
// Get Basis Theory credentials based on sandboxed flag from payment method config
|
|
19
|
+
// When sandboxed is true (even on production hostname), use test keys
|
|
20
|
+
const { basistheoryPublicKey, basistheoryTenantId } = useMemo(() => {
|
|
21
|
+
const useProductionKeys = googlePayPaymentMethod?.metadata?.sandboxed === false;
|
|
22
|
+
const keys = getBasisTheoryKeys(useProductionKeys);
|
|
23
|
+
console.log('[GooglePayButton] Using Basis Theory keys:', {
|
|
24
|
+
sandboxed: googlePayPaymentMethod?.metadata?.sandboxed,
|
|
25
|
+
useProductionKeys,
|
|
26
|
+
tenantId: keys.tenantId,
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
basistheoryPublicKey: keys.apiKey,
|
|
30
|
+
basistheoryTenantId: keys.tenantId,
|
|
31
|
+
};
|
|
32
|
+
}, [googlePayPaymentMethod?.metadata?.sandboxed]);
|
|
14
33
|
const [processingPayment, setProcessingPayment] = useState(false);
|
|
15
34
|
const [pendingPaymentData, setPendingPaymentData] = useState(null);
|
|
16
35
|
const [googlePayError, setGooglePayError] = useState(null);
|
|
@@ -65,46 +84,32 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
65
84
|
throw error;
|
|
66
85
|
}
|
|
67
86
|
}, []);
|
|
68
|
-
// Process Google Pay payment
|
|
69
|
-
const
|
|
70
|
-
if (!checkout.checkoutSession.id) {
|
|
71
|
-
throw new Error('Checkout session ID is not available');
|
|
72
|
-
}
|
|
87
|
+
// Process Google Pay payment using SDK hook
|
|
88
|
+
const handleGooglePayPayment = useCallback(async (token) => {
|
|
73
89
|
setProcessingPayment(true);
|
|
74
90
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
const result = await processGooglePayPayment(checkout.checkoutSession.id, token, {
|
|
92
|
+
onPaymentSuccess: (response) => {
|
|
93
|
+
// Keep processing state true during navigation
|
|
94
|
+
},
|
|
95
|
+
onPaymentFailed: (err) => {
|
|
96
|
+
setProcessingPayment(false);
|
|
97
|
+
setGooglePayError(err.message);
|
|
98
|
+
setContextError(err.message);
|
|
99
|
+
throw new Error(err.message);
|
|
80
100
|
},
|
|
81
|
-
body: JSON.stringify({
|
|
82
|
-
checkoutSessionId: checkout.checkoutSession.id,
|
|
83
|
-
paymentToken: token,
|
|
84
|
-
}),
|
|
85
101
|
});
|
|
86
|
-
|
|
87
|
-
throw new Error('Failed to process Google Pay payment');
|
|
88
|
-
}
|
|
89
|
-
const paymentResult = await response.json();
|
|
90
|
-
if (onSuccess) {
|
|
91
|
-
onSuccess(paymentResult);
|
|
92
|
-
}
|
|
102
|
+
return result;
|
|
93
103
|
}
|
|
94
104
|
catch (error) {
|
|
95
|
-
console.error('
|
|
105
|
+
console.error('Payment processing failed:', error);
|
|
106
|
+
setProcessingPayment(false);
|
|
96
107
|
const errorMessage = error instanceof Error ? error.message : 'Google Pay payment failed';
|
|
97
108
|
setGooglePayError(errorMessage);
|
|
98
109
|
setContextError(errorMessage);
|
|
99
|
-
if (onError) {
|
|
100
|
-
onError(errorMessage);
|
|
101
|
-
}
|
|
102
110
|
throw error;
|
|
103
111
|
}
|
|
104
|
-
|
|
105
|
-
setProcessingPayment(false);
|
|
106
|
-
}
|
|
107
|
-
}, [checkout.checkoutSession.id, onSuccess, onError, setContextError]);
|
|
112
|
+
}, [processGooglePayPayment, checkout.checkoutSession.id, setContextError]);
|
|
108
113
|
// Process payment data
|
|
109
114
|
const onGooglePaymentData = useCallback(async (paymentData) => {
|
|
110
115
|
setProcessingPayment(true);
|
|
@@ -137,7 +142,11 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
const payToken = await tokenizeGooglePayTokenWithBasisTheory(paymentData);
|
|
140
|
-
await
|
|
145
|
+
const result = await handleGooglePayPayment(payToken);
|
|
146
|
+
// Call success callback
|
|
147
|
+
if (onSuccess) {
|
|
148
|
+
onSuccess(result);
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
151
|
catch (error) {
|
|
143
152
|
console.error('Error processing Google Pay payment:', error);
|
|
@@ -154,7 +163,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
154
163
|
updateCheckoutSessionValues,
|
|
155
164
|
updateCustomerEmail,
|
|
156
165
|
tokenizeGooglePayTokenWithBasisTheory,
|
|
157
|
-
|
|
166
|
+
handleGooglePayPayment,
|
|
167
|
+
onSuccess,
|
|
158
168
|
onError,
|
|
159
169
|
setContextError,
|
|
160
170
|
]);
|
|
@@ -200,8 +210,12 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
200
210
|
});
|
|
201
211
|
const newOrderSummary = await reComputeOrderSummary();
|
|
202
212
|
if (newOrderSummary) {
|
|
213
|
+
// Use selected shipping rate ID if available, otherwise fall back to first one
|
|
214
|
+
const defaultSelectedId = newOrderSummary.selectedShippingRateId ||
|
|
215
|
+
newOrderSummary.shippingMethods[0]?.identifier ||
|
|
216
|
+
'';
|
|
203
217
|
paymentDataRequestUpdate.newShippingOptionParameters = {
|
|
204
|
-
defaultSelectedOptionId:
|
|
218
|
+
defaultSelectedOptionId: defaultSelectedId,
|
|
205
219
|
shippingOptions: newOrderSummary.shippingMethods.map((method) => ({
|
|
206
220
|
id: method.identifier,
|
|
207
221
|
label: method.label,
|
|
@@ -218,17 +232,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
218
232
|
else if (intermediatePaymentData.callbackTrigger === 'SHIPPING_OPTION') {
|
|
219
233
|
// Update shipping rate
|
|
220
234
|
if (intermediatePaymentData.shippingOptionData?.id) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
headers: {
|
|
224
|
-
'Content-Type': 'application/json',
|
|
225
|
-
},
|
|
226
|
-
body: JSON.stringify({
|
|
227
|
-
checkoutSessionId: checkout.checkoutSession.id,
|
|
228
|
-
shippingRateId: intermediatePaymentData.shippingOptionData.id,
|
|
229
|
-
}),
|
|
230
|
-
});
|
|
231
|
-
if (response.ok) {
|
|
235
|
+
try {
|
|
236
|
+
await selectRate(intermediatePaymentData.shippingOptionData.id);
|
|
232
237
|
const newOrderSummary = await reComputeOrderSummary();
|
|
233
238
|
if (newOrderSummary) {
|
|
234
239
|
paymentDataRequestUpdate.newTransactionInfo = {
|
|
@@ -238,13 +243,17 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
238
243
|
};
|
|
239
244
|
}
|
|
240
245
|
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error('Error selecting shipping rate:', error);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
241
250
|
}
|
|
242
251
|
}
|
|
243
252
|
else if (intermediatePaymentData.callbackTrigger === 'OFFER') {
|
|
244
|
-
|
|
253
|
+
// No action needed for OFFER callback
|
|
245
254
|
}
|
|
246
255
|
else if (intermediatePaymentData.callbackTrigger === 'INITIALIZE') {
|
|
247
|
-
|
|
256
|
+
// No action needed for INITIALIZE callback
|
|
248
257
|
}
|
|
249
258
|
resolve(paymentDataRequestUpdate);
|
|
250
259
|
}
|
|
@@ -261,7 +270,7 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
261
270
|
};
|
|
262
271
|
void processCallback();
|
|
263
272
|
});
|
|
264
|
-
}, [updateCheckoutSessionValues, reComputeOrderSummary, checkout]);
|
|
273
|
+
}, [updateCheckoutSessionValues, reComputeOrderSummary, checkout, selectRate]);
|
|
265
274
|
// Handle payment authorization
|
|
266
275
|
const handleGooglePayAuthorized = useCallback((paymentData) => {
|
|
267
276
|
setPendingPaymentData(paymentData);
|
|
@@ -295,6 +304,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
295
304
|
gatewayMerchantId: basistheoryTenantId,
|
|
296
305
|
},
|
|
297
306
|
};
|
|
307
|
+
// Determine if shipping is required: use prop if provided, otherwise auto-detect from shippingMethods
|
|
308
|
+
const requiresShipping = requiresShippingProp ?? shippingMethods.length > 0;
|
|
298
309
|
const paymentRequest = {
|
|
299
310
|
apiVersion: 2,
|
|
300
311
|
apiVersionMinor: 0,
|
|
@@ -313,22 +324,28 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
313
324
|
merchantName: googlePayPaymentMethod?.metadata?.merchantName ||
|
|
314
325
|
checkout.checkoutSession?.store?.name ||
|
|
315
326
|
'Store',
|
|
327
|
+
// Use test merchant ID for sandbox, actual merchant ID for production
|
|
316
328
|
merchantId: googlePayPaymentMethod?.metadata?.sandboxed
|
|
317
|
-
? '12345678901234567890'
|
|
329
|
+
? '12345678901234567890' // Google's test merchant ID for sandbox
|
|
318
330
|
: googlePayPaymentMethod?.metadata?.merchantId || '12345678901234567890',
|
|
319
331
|
},
|
|
320
|
-
shippingAddressRequired:
|
|
321
|
-
shippingOptionRequired:
|
|
332
|
+
shippingAddressRequired: requiresShipping,
|
|
333
|
+
shippingOptionRequired: requiresShipping,
|
|
322
334
|
emailRequired: true,
|
|
323
|
-
callbackIntents:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
callbackIntents: requiresShipping
|
|
336
|
+
? ['SHIPPING_OPTION', 'SHIPPING_ADDRESS', 'PAYMENT_AUTHORIZATION']
|
|
337
|
+
: ['PAYMENT_AUTHORIZATION'],
|
|
338
|
+
...(requiresShipping && {
|
|
339
|
+
shippingOptionParameters: {
|
|
340
|
+
defaultSelectedOptionId: checkout.checkoutSession?.shippingRate?.id ||
|
|
341
|
+
(shippingMethods.length > 0 ? shippingMethods[0].identifier : ''),
|
|
342
|
+
shippingOptions: shippingMethods.map((method) => ({
|
|
343
|
+
id: method.identifier,
|
|
344
|
+
label: method.label,
|
|
345
|
+
description: method.amount + ': ' + method.detail || '',
|
|
346
|
+
})),
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
332
349
|
};
|
|
333
350
|
const environment = googlePayPaymentMethod?.metadata?.sandboxed ? 'TEST' : 'PRODUCTION';
|
|
334
351
|
// Button size classes
|
|
@@ -347,7 +364,6 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
347
364
|
onError(errorMessage);
|
|
348
365
|
}
|
|
349
366
|
}, onCancel: () => {
|
|
350
|
-
console.log('Google Pay payment cancelled');
|
|
351
367
|
if (onCancel) {
|
|
352
368
|
onCancel();
|
|
353
369
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Mode Indicator
|
|
3
|
+
*
|
|
4
|
+
* Visual indicator shown when the SDK is in draft/preview mode
|
|
5
|
+
* Helps users distinguish between preview and production environments
|
|
6
|
+
*/
|
|
7
|
+
export interface PreviewModeIndicatorProps {
|
|
8
|
+
/**
|
|
9
|
+
* Position of the indicator
|
|
10
|
+
* @default 'bottom-right'
|
|
11
|
+
*/
|
|
12
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
13
|
+
/**
|
|
14
|
+
* Custom className for styling
|
|
15
|
+
*/
|
|
16
|
+
className?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Show detailed info on hover
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
showDetails?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Preview Mode Indicator Component
|
|
25
|
+
*
|
|
26
|
+
* Automatically shows when:
|
|
27
|
+
* - Draft mode is enabled (draft=true)
|
|
28
|
+
* - Funnel tracking is disabled (funnelTracking=false)
|
|
29
|
+
* - Custom API environment is set (tagadaClientEnv)
|
|
30
|
+
* - Custom API base URL is set (tagadaClientBaseUrl)
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { PreviewModeIndicator } from '@tagadapay/plugin-sdk';
|
|
35
|
+
*
|
|
36
|
+
* function App() {
|
|
37
|
+
* return (
|
|
38
|
+
* <>
|
|
39
|
+
* <PreviewModeIndicator />
|
|
40
|
+
* {/* Your app content *\/}
|
|
41
|
+
* </>
|
|
42
|
+
* );
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function PreviewModeIndicator({ position, className, showDetails, }?: PreviewModeIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Preview Mode Indicator
|
|
4
|
+
*
|
|
5
|
+
* Visual indicator shown when the SDK is in draft/preview mode
|
|
6
|
+
* Helps users distinguish between preview and production environments
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useState } from 'react';
|
|
9
|
+
import { isDraftMode, isFunnelTrackingEnabled, getSDKParams } from '../../core/utils/previewMode';
|
|
10
|
+
/**
|
|
11
|
+
* Preview Mode Indicator Component
|
|
12
|
+
*
|
|
13
|
+
* Automatically shows when:
|
|
14
|
+
* - Draft mode is enabled (draft=true)
|
|
15
|
+
* - Funnel tracking is disabled (funnelTracking=false)
|
|
16
|
+
* - Custom API environment is set (tagadaClientEnv)
|
|
17
|
+
* - Custom API base URL is set (tagadaClientBaseUrl)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { PreviewModeIndicator } from '@tagadapay/plugin-sdk';
|
|
22
|
+
*
|
|
23
|
+
* function App() {
|
|
24
|
+
* return (
|
|
25
|
+
* <>
|
|
26
|
+
* <PreviewModeIndicator />
|
|
27
|
+
* {/* Your app content *\/}
|
|
28
|
+
* </>
|
|
29
|
+
* );
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function PreviewModeIndicator({ position = 'bottom-right', className, showDetails = true, } = {}) {
|
|
34
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
35
|
+
const [params, setParams] = useState({});
|
|
36
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
// Check if we're in preview mode
|
|
39
|
+
const sdkParams = getSDKParams();
|
|
40
|
+
const draftMode = isDraftMode();
|
|
41
|
+
const trackingDisabled = !isFunnelTrackingEnabled();
|
|
42
|
+
const hasCustomEnv = !!(sdkParams.tagadaClientEnv || sdkParams.tagadaClientBaseUrl);
|
|
43
|
+
setParams(sdkParams);
|
|
44
|
+
setIsVisible(draftMode || trackingDisabled || hasCustomEnv);
|
|
45
|
+
}, []);
|
|
46
|
+
if (!isVisible)
|
|
47
|
+
return null;
|
|
48
|
+
const positionStyles = {
|
|
49
|
+
'top-left': { top: '16px', left: '16px' },
|
|
50
|
+
'top-right': { top: '16px', right: '16px' },
|
|
51
|
+
'bottom-left': { bottom: '16px', left: '16px' },
|
|
52
|
+
'bottom-right': { bottom: '16px', right: '16px' },
|
|
53
|
+
};
|
|
54
|
+
const isDraft = isDraftMode();
|
|
55
|
+
const trackingDisabled = !isFunnelTrackingEnabled();
|
|
56
|
+
return (_jsxs("div", { className: className, style: {
|
|
57
|
+
position: 'fixed',
|
|
58
|
+
zIndex: 999999,
|
|
59
|
+
...positionStyles[position],
|
|
60
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
61
|
+
}, onMouseEnter: () => showDetails && setIsExpanded(true), onMouseLeave: () => setIsExpanded(false), children: [_jsxs("div", { style: {
|
|
62
|
+
background: isDraft ? '#ff9500' : '#007aff',
|
|
63
|
+
color: 'white',
|
|
64
|
+
padding: '8px 12px',
|
|
65
|
+
borderRadius: '8px',
|
|
66
|
+
fontSize: '13px',
|
|
67
|
+
fontWeight: '600',
|
|
68
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
|
69
|
+
cursor: showDetails ? 'pointer' : 'default',
|
|
70
|
+
transition: 'all 0.2s ease',
|
|
71
|
+
display: 'flex',
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
gap: '6px',
|
|
74
|
+
}, children: [_jsx("span", { style: { fontSize: '16px' }, children: "\uD83D\uDD0D" }), _jsx("span", { children: isDraft ? 'Preview Mode' : 'Dev Mode' })] }), showDetails && isExpanded && (_jsxs("div", { style: {
|
|
75
|
+
position: 'absolute',
|
|
76
|
+
bottom: position.includes('bottom') ? 'calc(100% + 8px)' : undefined,
|
|
77
|
+
top: position.includes('top') ? 'calc(100% + 8px)' : undefined,
|
|
78
|
+
right: position.includes('right') ? 0 : undefined,
|
|
79
|
+
left: position.includes('left') ? 0 : undefined,
|
|
80
|
+
background: 'white',
|
|
81
|
+
border: '1px solid #e5e5e5',
|
|
82
|
+
borderRadius: '8px',
|
|
83
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
84
|
+
padding: '12px',
|
|
85
|
+
minWidth: '250px',
|
|
86
|
+
fontSize: '12px',
|
|
87
|
+
lineHeight: '1.5',
|
|
88
|
+
}, children: [_jsx("div", { style: { marginBottom: '8px', fontWeight: '600', color: '#1d1d1f' }, children: "Current Environment" }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '6px' }, children: [isDraft && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', color: '#86868b' }, children: [_jsx("span", { children: "Draft Mode:" }), _jsx("span", { style: { color: '#ff9500', fontWeight: '600' }, children: "ON" })] })), trackingDisabled && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', color: '#86868b' }, children: [_jsx("span", { children: "Tracking:" }), _jsx("span", { style: { color: '#ff3b30', fontWeight: '600' }, children: "DISABLED" })] })), params.funnelEnv && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', color: '#86868b' }, children: [_jsx("span", { children: "Funnel Env:" }), _jsx("span", { style: { color: '#1d1d1f', fontWeight: '600', fontFamily: 'monospace', fontSize: '11px' }, children: params.funnelEnv })] })), params.tagadaClientEnv && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', color: '#86868b' }, children: [_jsx("span", { children: "API Env:" }), _jsx("span", { style: { color: '#1d1d1f', fontWeight: '600', fontFamily: 'monospace', fontSize: '11px' }, children: params.tagadaClientEnv })] })), params.tagadaClientBaseUrl && (_jsxs("div", { style: { color: '#86868b' }, children: [_jsx("div", { style: { marginBottom: '4px' }, children: "API URL:" }), _jsx("div", { style: {
|
|
89
|
+
color: '#1d1d1f',
|
|
90
|
+
fontWeight: '600',
|
|
91
|
+
fontFamily: 'monospace',
|
|
92
|
+
fontSize: '10px',
|
|
93
|
+
wordBreak: 'break-all',
|
|
94
|
+
background: '#f5f5f7',
|
|
95
|
+
padding: '4px 6px',
|
|
96
|
+
borderRadius: '4px',
|
|
97
|
+
}, children: params.tagadaClientBaseUrl })] })), params.funnelId && (_jsxs("div", { style: { color: '#86868b', marginTop: '4px', paddingTop: '8px', borderTop: '1px solid #e5e5e5' }, children: [_jsx("div", { style: { marginBottom: '4px' }, children: "Funnel ID:" }), _jsx("div", { style: {
|
|
98
|
+
color: '#1d1d1f',
|
|
99
|
+
fontFamily: 'monospace',
|
|
100
|
+
fontSize: '10px',
|
|
101
|
+
wordBreak: 'break-all',
|
|
102
|
+
background: '#f5f5f7',
|
|
103
|
+
padding: '4px 6px',
|
|
104
|
+
borderRadius: '4px',
|
|
105
|
+
}, children: params.funnelId })] }))] }), _jsxs("div", { style: {
|
|
106
|
+
marginTop: '12px',
|
|
107
|
+
paddingTop: '8px',
|
|
108
|
+
borderTop: '1px solid #e5e5e5',
|
|
109
|
+
fontSize: '11px',
|
|
110
|
+
color: '#86868b',
|
|
111
|
+
textAlign: 'center',
|
|
112
|
+
}, children: ["Add ", _jsx("code", { style: { background: '#f5f5f7', padding: '2px 4px', borderRadius: '3px' }, children: "?forceReset=true" }), " to reset"] })] }))] }));
|
|
113
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CheckoutData } from '../../core/resources/checkout';
|
|
2
|
+
export interface UseApplePayCheckoutOptions {
|
|
3
|
+
checkout: CheckoutData | undefined;
|
|
4
|
+
onSuccess?: (result: {
|
|
5
|
+
payment: any;
|
|
6
|
+
order?: any;
|
|
7
|
+
}) => void;
|
|
8
|
+
onError?: (error: string) => void;
|
|
9
|
+
onCancel?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function useApplePayCheckout({ checkout, onSuccess, onError, onCancel, }: UseApplePayCheckoutOptions): {
|
|
12
|
+
handleApplePayClick: () => void;
|
|
13
|
+
processingPayment: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
clearError: () => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { usePaymentQuery } from './usePaymentQuery';
|
|
3
|
+
import { getBasisTheoryApiKey } from '../../../react/config/payment';
|
|
4
|
+
export function useApplePayCheckout({ checkout, onSuccess, onError, onCancel, }) {
|
|
5
|
+
const [processingPayment, setProcessingPayment] = useState(false);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
const { processApplePayPayment } = usePaymentQuery();
|
|
8
|
+
const basistheoryPublicKey = useMemo(() => getBasisTheoryApiKey(), []);
|
|
9
|
+
// Validate merchant with Basis Theory
|
|
10
|
+
const validateMerchant = useCallback(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch('https://api.basistheory.com/apple-pay/session', {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'BT-API-KEY': basistheoryPublicKey,
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
display_name: checkout?.checkoutSession?.store?.name || 'Store',
|
|
20
|
+
domain: window.location.host,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
return await response.json();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error('Merchant validation failed:', err);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}, [basistheoryPublicKey, checkout?.checkoutSession?.store?.name]);
|
|
33
|
+
// Tokenize Apple Pay payment
|
|
34
|
+
const tokenizeApplePay = useCallback(async (event) => {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch('https://api.basistheory.com/apple-pay', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'BT-API-KEY': basistheoryPublicKey,
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
apple_payment_data: event.payment.token,
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const result = await response.json();
|
|
50
|
+
return result.apple_pay;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error('Tokenization failed:', err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}, [basistheoryPublicKey]);
|
|
57
|
+
// Handle Apple Pay payment click
|
|
58
|
+
const handleApplePayClick = useCallback(() => {
|
|
59
|
+
// Don't proceed if checkout is not available
|
|
60
|
+
if (!checkout) {
|
|
61
|
+
console.error('Checkout data not available');
|
|
62
|
+
if (onError) {
|
|
63
|
+
onError('Checkout not ready');
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Create line items from checkout summary
|
|
68
|
+
const lineItems = [
|
|
69
|
+
{
|
|
70
|
+
label: 'Subtotal',
|
|
71
|
+
amount: (checkout.summary.subtotalAdjustedAmount / 100).toFixed(2),
|
|
72
|
+
type: 'final',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: 'Shipping',
|
|
76
|
+
amount: ((checkout.summary.shippingCost ?? 0) / 100).toFixed(2),
|
|
77
|
+
type: 'final',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: 'Tax',
|
|
81
|
+
amount: (checkout.summary.totalTaxAmount / 100).toFixed(2),
|
|
82
|
+
type: 'final',
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
const total = {
|
|
86
|
+
label: checkout.checkoutSession.store?.name || 'Store',
|
|
87
|
+
amount: (checkout.summary.totalAdjustedAmount / 100).toFixed(2),
|
|
88
|
+
type: 'final',
|
|
89
|
+
};
|
|
90
|
+
const request = {
|
|
91
|
+
countryCode: 'US', // Could be from payment method metadata
|
|
92
|
+
currencyCode: checkout.summary.currency,
|
|
93
|
+
supportedNetworks: ['visa', 'masterCard', 'amex', 'discover'],
|
|
94
|
+
merchantCapabilities: ['supports3DS'],
|
|
95
|
+
total,
|
|
96
|
+
lineItems,
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const session = new window.ApplePaySession(3, request);
|
|
100
|
+
// Merchant validation
|
|
101
|
+
session.onvalidatemerchant = (event) => {
|
|
102
|
+
void (async () => {
|
|
103
|
+
try {
|
|
104
|
+
const merchantSession = await validateMerchant();
|
|
105
|
+
session.completeMerchantValidation(merchantSession);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error('Merchant validation failed:', error);
|
|
109
|
+
session.abort();
|
|
110
|
+
if (onError) {
|
|
111
|
+
onError('Merchant validation failed');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
};
|
|
116
|
+
// Payment authorized
|
|
117
|
+
session.onpaymentauthorized = (event) => {
|
|
118
|
+
void (async () => {
|
|
119
|
+
try {
|
|
120
|
+
setProcessingPayment(true);
|
|
121
|
+
// Tokenize payment
|
|
122
|
+
const applePayToken = await tokenizeApplePay(event);
|
|
123
|
+
// Complete Apple Pay sheet
|
|
124
|
+
session.completePayment(window.ApplePaySession.STATUS_SUCCESS);
|
|
125
|
+
// Process payment via SDK hook
|
|
126
|
+
const result = await processApplePayPayment(checkout.checkoutSession.id, applePayToken, {
|
|
127
|
+
onPaymentSuccess: (response) => {
|
|
128
|
+
// Keep processing state true during navigation
|
|
129
|
+
},
|
|
130
|
+
onPaymentFailed: (err) => {
|
|
131
|
+
setProcessingPayment(false);
|
|
132
|
+
setError(err.message);
|
|
133
|
+
if (onError) {
|
|
134
|
+
onError(err.message);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
// Call success callback
|
|
139
|
+
if (onSuccess) {
|
|
140
|
+
onSuccess(result);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error('Payment failed:', error);
|
|
145
|
+
session.completePayment(window.ApplePaySession.STATUS_FAILURE);
|
|
146
|
+
setProcessingPayment(false);
|
|
147
|
+
const errorMsg = error instanceof Error ? error.message : 'Payment failed';
|
|
148
|
+
setError(errorMsg);
|
|
149
|
+
if (onError) {
|
|
150
|
+
onError(errorMsg);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})();
|
|
154
|
+
};
|
|
155
|
+
// Handle cancellation
|
|
156
|
+
session.oncancel = () => {
|
|
157
|
+
console.log('Apple Pay cancelled by user');
|
|
158
|
+
setProcessingPayment(false);
|
|
159
|
+
if (onCancel) {
|
|
160
|
+
onCancel();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
// Handle errors
|
|
164
|
+
session.onerror = (event) => {
|
|
165
|
+
console.error('Apple Pay session error:', event);
|
|
166
|
+
setProcessingPayment(false);
|
|
167
|
+
};
|
|
168
|
+
session.begin();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error('Failed to start Apple Pay session:', error);
|
|
172
|
+
const errorMsg = 'Failed to start Apple Pay';
|
|
173
|
+
setError(errorMsg);
|
|
174
|
+
if (onError) {
|
|
175
|
+
onError(errorMsg);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, [
|
|
179
|
+
checkout,
|
|
180
|
+
validateMerchant,
|
|
181
|
+
tokenizeApplePay,
|
|
182
|
+
processApplePayPayment,
|
|
183
|
+
onSuccess,
|
|
184
|
+
onError,
|
|
185
|
+
onCancel,
|
|
186
|
+
]);
|
|
187
|
+
return {
|
|
188
|
+
handleApplePayClick,
|
|
189
|
+
processingPayment,
|
|
190
|
+
error,
|
|
191
|
+
clearError: () => setError(null),
|
|
192
|
+
};
|
|
193
|
+
}
|