@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.
Files changed (71) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +220 -113
  3. package/dist/external-tracker.js +1225 -558
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +4 -4
  6. package/dist/react/hooks/useApplePay.js +25 -36
  7. package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
  8. package/dist/react/providers/TagadaProvider.js +5 -5
  9. package/dist/react/utils/money.d.ts +4 -3
  10. package/dist/react/utils/money.js +39 -6
  11. package/dist/react/utils/trackingUtils.js +1 -0
  12. package/dist/tagada-sdk.js +10142 -0
  13. package/dist/tagada-sdk.min.js +43 -0
  14. package/dist/tagada-sdk.min.js.map +7 -0
  15. package/dist/v2/core/client.js +34 -2
  16. package/dist/v2/core/config/environment.js +9 -2
  17. package/dist/v2/core/funnelClient.d.ts +180 -2
  18. package/dist/v2/core/funnelClient.js +289 -6
  19. package/dist/v2/core/resources/apiClient.js +1 -1
  20. package/dist/v2/core/resources/checkout.d.ts +68 -0
  21. package/dist/v2/core/resources/funnel.d.ts +25 -0
  22. package/dist/v2/core/resources/payments.d.ts +70 -3
  23. package/dist/v2/core/resources/payments.js +72 -7
  24. package/dist/v2/core/utils/index.d.ts +1 -0
  25. package/dist/v2/core/utils/index.js +2 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  27. package/dist/v2/core/utils/pluginConfig.js +68 -5
  28. package/dist/v2/core/utils/previewMode.d.ts +7 -0
  29. package/dist/v2/core/utils/previewMode.js +72 -14
  30. package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
  31. package/dist/v2/core/utils/previewModeIndicator.js +414 -0
  32. package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
  33. package/dist/v2/core/utils/tokenStorage.js +15 -1
  34. package/dist/v2/index.d.ts +9 -3
  35. package/dist/v2/index.js +8 -3
  36. package/dist/v2/react/components/ApplePayButton.d.ts +22 -123
  37. package/dist/v2/react/components/ApplePayButton.js +247 -317
  38. package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
  39. package/dist/v2/react/components/FunnelScriptInjector.js +255 -162
  40. package/dist/v2/react/components/GooglePayButton.d.ts +2 -0
  41. package/dist/v2/react/components/GooglePayButton.js +80 -64
  42. package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
  43. package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
  44. package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
  45. package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
  46. package/dist/v2/react/hooks/useFunnel.d.ts +48 -6
  47. package/dist/v2/react/hooks/useFunnel.js +25 -5
  48. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +10 -0
  49. package/dist/v2/react/hooks/useGoogleAutocomplete.js +48 -0
  50. package/dist/v2/react/hooks/useGooglePayCheckout.d.ts +21 -0
  51. package/dist/v2/react/hooks/useGooglePayCheckout.js +198 -0
  52. package/dist/v2/react/hooks/usePaymentPolling.d.ts +15 -3
  53. package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
  54. package/dist/v2/react/hooks/usePaymentQuery.d.ts +34 -2
  55. package/dist/v2/react/hooks/usePaymentQuery.js +731 -7
  56. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
  57. package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
  58. package/dist/v2/react/hooks/usePixelTracking.d.ts +56 -0
  59. package/dist/v2/react/hooks/usePixelTracking.js +508 -0
  60. package/dist/v2/react/hooks/useStepConfig.d.ts +64 -0
  61. package/dist/v2/react/hooks/useStepConfig.js +53 -0
  62. package/dist/v2/react/index.d.ts +15 -5
  63. package/dist/v2/react/index.js +8 -2
  64. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
  65. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +41 -13
  66. package/dist/v2/react/providers/TagadaProvider.js +24 -23
  67. package/dist/v2/standalone/external-tracker.d.ts +2 -0
  68. package/dist/v2/standalone/external-tracker.js +6 -3
  69. package/package.json +112 -112
  70. package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
  71. 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
- const basistheoryPublicKey = (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY) || '';
10
- const basistheoryTenantId = (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BASIS_THEORY_TENANT_ID) ||
11
- '0b283fa3-44a1-4535-adff-e99ad0a58a47';
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 processGooglePayPayment = useCallback(async (token) => {
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
- // Process payment with backend API
76
- const response = await fetch('/api/v1/google-pay/process-payment', {
77
- method: 'POST',
78
- headers: {
79
- 'Content-Type': 'application/json',
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
- if (!response.ok) {
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('Error processing Google Pay payment:', 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
- finally {
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 processGooglePayPayment(payToken);
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
- processGooglePayPayment,
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: newOrderSummary.shippingMethods[0]?.identifier || '',
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
- const response = await fetch('/api/v1/shipping-rates/select', {
222
- method: 'POST',
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
- console.log('OFFER callback triggered, no action needed');
253
+ // No action needed for OFFER callback
245
254
  }
246
255
  else if (intermediatePaymentData.callbackTrigger === 'INITIALIZE') {
247
- console.log('INITIALIZE callback triggered, no action needed');
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: true,
321
- shippingOptionRequired: true,
332
+ shippingAddressRequired: requiresShipping,
333
+ shippingOptionRequired: requiresShipping,
322
334
  emailRequired: true,
323
- callbackIntents: ['SHIPPING_OPTION', 'SHIPPING_ADDRESS', 'PAYMENT_AUTHORIZATION'],
324
- shippingOptionParameters: {
325
- defaultSelectedOptionId: shippingMethods.length > 0 ? shippingMethods[0].identifier : '',
326
- shippingOptions: shippingMethods.map((method) => ({
327
- id: method.identifier,
328
- label: method.label,
329
- description: method.amount + ': ' + method.detail || '',
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
+ }