@tagadapay/plugin-sdk 3.1.12 → 3.1.22

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 (106) hide show
  1. package/build-cdn.js +129 -11
  2. package/dist/data/iso3166.d.ts +23 -33
  3. package/dist/data/iso3166.js +134 -198
  4. package/dist/data/languages.d.ts +5 -64
  5. package/dist/data/languages.js +23 -143
  6. package/dist/external-tracker.js +968 -101
  7. package/dist/external-tracker.min.js +2 -2
  8. package/dist/external-tracker.min.js.map +4 -4
  9. package/dist/react/hooks/useISOData.js +1 -1
  10. package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
  11. package/dist/tagada-sdk.js +12066 -0
  12. package/dist/tagada-sdk.min.js +50 -0
  13. package/dist/tagada-sdk.min.js.map +7 -0
  14. package/dist/v2/core/client.d.ts +4 -2
  15. package/dist/v2/core/client.js +4 -3
  16. package/dist/v2/core/errors.d.ts +75 -0
  17. package/dist/v2/core/errors.js +104 -0
  18. package/dist/v2/core/funnelClient.d.ts +2 -0
  19. package/dist/v2/core/index.d.ts +1 -0
  20. package/dist/v2/core/index.js +2 -0
  21. package/dist/v2/core/pixelMapping.d.ts +49 -0
  22. package/dist/v2/core/pixelMapping.js +325 -0
  23. package/dist/v2/core/resources/apiClient.d.ts +2 -0
  24. package/dist/v2/core/resources/apiClient.js +52 -9
  25. package/dist/v2/core/resources/checkout.d.ts +89 -30
  26. package/dist/v2/core/resources/checkout.js +8 -0
  27. package/dist/v2/core/resources/customer.d.ts +20 -19
  28. package/dist/v2/core/resources/funnel.d.ts +17 -17
  29. package/dist/v2/core/resources/payments.d.ts +84 -13
  30. package/dist/v2/core/resources/payments.js +26 -9
  31. package/dist/v2/core/types.d.ts +50 -12
  32. package/dist/v2/core/types.js +0 -3
  33. package/dist/v2/core/utils/checkout.d.ts +2 -2
  34. package/dist/v2/core/utils/checkout.js +7 -2
  35. package/dist/v2/core/utils/currency.d.ts +14 -0
  36. package/dist/v2/core/utils/currency.js +40 -0
  37. package/dist/v2/core/utils/index.d.ts +1 -0
  38. package/dist/v2/core/utils/index.js +2 -0
  39. package/dist/v2/core/utils/order.d.ts +11 -9
  40. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  41. package/dist/v2/core/utils/pluginConfig.js +28 -0
  42. package/dist/v2/index.d.ts +3 -1
  43. package/dist/v2/index.js +1 -1
  44. package/dist/v2/react/components/FunnelScriptInjector.js +21 -0
  45. package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
  46. package/dist/v2/react/components/WhopCheckout.js +231 -0
  47. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
  48. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
  49. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
  50. package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
  51. package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
  52. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
  53. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
  54. package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
  55. package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
  56. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
  57. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
  58. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
  59. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
  60. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
  61. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +13 -0
  62. package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
  63. package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
  64. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
  65. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
  66. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
  67. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
  68. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
  69. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
  70. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
  71. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
  72. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
  73. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
  74. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
  75. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
  76. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
  77. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
  78. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +6 -0
  79. package/dist/v2/react/hooks/useCheckoutQuery.js +45 -0
  80. package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
  81. package/dist/v2/react/hooks/useGeoLocation.js +4 -2
  82. package/dist/v2/react/hooks/useISOData.js +1 -1
  83. package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
  84. package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
  85. package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
  86. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
  87. package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
  88. package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
  89. package/dist/v2/react/hooks/usePixelTracking.js +212 -514
  90. package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
  91. package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
  92. package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
  93. package/dist/v2/react/index.d.ts +7 -0
  94. package/dist/v2/react/index.js +4 -0
  95. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +2 -1
  96. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +3 -1
  97. package/dist/v2/react/providers/TagadaProvider.js +71 -2
  98. package/dist/v2/standalone/external-tracker.d.ts +52 -46
  99. package/dist/v2/standalone/external-tracker.js +205 -98
  100. package/dist/v2/standalone/index.d.ts +22 -0
  101. package/dist/v2/standalone/index.js +126 -1
  102. package/package.json +3 -3
  103. package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
  104. package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
  105. package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
  106. package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
@@ -0,0 +1,24 @@
1
+ import type { WhopPayment } from '../hooks/useWhopPaymentPolling';
2
+ export interface WhopCheckoutHandle {
3
+ submit: () => Promise<void>;
4
+ }
5
+ export interface WhopCheckoutProps {
6
+ checkoutSessionId: string;
7
+ storeId: string;
8
+ customerEmail?: string;
9
+ shippingAddress?: {
10
+ firstName?: string;
11
+ lastName?: string;
12
+ country?: string;
13
+ address1?: string;
14
+ city?: string;
15
+ state?: string;
16
+ postal?: string;
17
+ };
18
+ orderItemsCount?: number;
19
+ orderTotalAmount?: number;
20
+ onPaymentCompleted?: (payment: WhopPayment) => void;
21
+ onPaymentFailed?: (error: string) => void;
22
+ onReady?: () => void;
23
+ }
24
+ export declare const WhopCheckout: import("react").NamedExoticComponent<WhopCheckoutProps & import("react").RefAttributes<WhopCheckoutHandle>>;
@@ -0,0 +1,231 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react';
3
+ import { useCheckoutEmbedControls, WhopCheckoutEmbed } from '@whop/checkout/react';
4
+ import { useTagadaContext } from '../providers/TagadaProvider';
5
+ import { useTranslation } from '../hooks/useTranslation';
6
+ import { useWhopPaymentPolling } from '../hooks/useWhopPaymentPolling';
7
+ export const WhopCheckout = memo(forwardRef(({ checkoutSessionId, storeId, customerEmail, shippingAddress, orderItemsCount, orderTotalAmount, onPaymentCompleted, onPaymentFailed, onReady, }, ref) => {
8
+ const { t } = useTranslation();
9
+ const { apiService } = useTagadaContext();
10
+ const whopRef = useCheckoutEmbedControls();
11
+ const isWhopPaymentSubmitted = useRef(false);
12
+ const orderIdRef = useRef(undefined);
13
+ const [planId, setPlanId] = useState(undefined);
14
+ const [isLoadingSession, setIsLoadingSession] = useState(true);
15
+ const [isPaymentLoading, setIsPaymentLoading] = useState(false);
16
+ const [loading, setLoading] = useState(false);
17
+ const [error, setError] = useState(null);
18
+ const isSessionInitiated = useRef(false);
19
+ // Initialize Whop session to get planId
20
+ const initiateWhopSession = async () => {
21
+ setIsLoadingSession(true);
22
+ try {
23
+ const data = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/whop-payment`, {
24
+ method: 'POST',
25
+ body: JSON.stringify({ checkoutSessionId, storeId }),
26
+ headers: { 'Content-Type': 'application/json' },
27
+ });
28
+ if (data?.planId) {
29
+ setPlanId(data.planId);
30
+ }
31
+ }
32
+ catch (err) {
33
+ console.error('Failed to initialize Whop session:', err);
34
+ setError('Failed to initialize checkout. Please try again.');
35
+ }
36
+ finally {
37
+ setIsLoadingSession(false);
38
+ }
39
+ };
40
+ useEffect(() => {
41
+ if (!checkoutSessionId || !storeId || isSessionInitiated.current)
42
+ return;
43
+ isSessionInitiated.current = true;
44
+ initiateWhopSession();
45
+ }, [checkoutSessionId, storeId]);
46
+ // Re-initiate Whop session when order summary changes (items added/removed, coupons, etc.)
47
+ useEffect(() => {
48
+ if (!checkoutSessionId || !orderItemsCount || !planId)
49
+ return;
50
+ console.log('Regenerating Whop plan due to order changes...');
51
+ isEmbedReady.current = false;
52
+ initiateWhopSession();
53
+ }, [orderItemsCount, orderTotalAmount]);
54
+ // Sync address/email to the Whop embed
55
+ const isEmbedReady = useRef(false);
56
+ const syncAddressAndEmail = () => {
57
+ if (!whopRef.current)
58
+ return;
59
+ if (shippingAddress) {
60
+ const address = {
61
+ name: `${shippingAddress.firstName || ''} ${shippingAddress.lastName || ''}`.trim(),
62
+ country: shippingAddress.country || '',
63
+ line1: shippingAddress.address1 || '',
64
+ city: shippingAddress.city || '',
65
+ state: shippingAddress.state || '',
66
+ postalCode: shippingAddress.postal || '',
67
+ };
68
+ if (typeof whopRef.current.setAddress === 'function') {
69
+ try {
70
+ whopRef.current.setAddress(address);
71
+ }
72
+ catch {
73
+ // Whop embed may not be ready yet
74
+ }
75
+ }
76
+ }
77
+ if (customerEmail && typeof whopRef.current.setEmail === 'function') {
78
+ try {
79
+ whopRef.current.setEmail(customerEmail);
80
+ }
81
+ catch {
82
+ // Whop embed may not be ready yet
83
+ }
84
+ }
85
+ };
86
+ // Re-sync when address/email props change after embed is ready
87
+ useEffect(() => {
88
+ if (!isEmbedReady.current)
89
+ return;
90
+ syncAddressAndEmail();
91
+ }, [shippingAddress, customerEmail]);
92
+ // Payment polling
93
+ const { startPolling, isPolling } = useWhopPaymentPolling({
94
+ apiService,
95
+ checkoutSessionId,
96
+ maxAttempts: 60,
97
+ intervalMs: 2000,
98
+ onMaxAttemptsReached: () => {
99
+ isWhopPaymentSubmitted.current = false;
100
+ setIsPaymentLoading(false);
101
+ setError('Payment verification timeout. Please check your email for order confirmation.');
102
+ onPaymentFailed?.('Payment verification timeout.');
103
+ },
104
+ onSuccess: (payment) => {
105
+ isWhopPaymentSubmitted.current = false;
106
+ setIsPaymentLoading(false);
107
+ onPaymentCompleted?.(payment);
108
+ },
109
+ onError: (err) => {
110
+ isWhopPaymentSubmitted.current = false;
111
+ setIsPaymentLoading(false);
112
+ setError('Unable to verify payment. Please check your email for order confirmation.');
113
+ onPaymentFailed?.(err.message);
114
+ },
115
+ });
116
+ // Submit payment handler - called from parent via ref
117
+ const handleWhopPayment = async () => {
118
+ if (isWhopPaymentSubmitted.current) {
119
+ console.warn('Whop payment already submitted.');
120
+ return;
121
+ }
122
+ if (!whopRef.current) {
123
+ console.error('Whop is not available. Please refresh the page.');
124
+ setError('Whop checkout is not available. Please refresh the page.');
125
+ onPaymentFailed?.('Whop checkout is not available.');
126
+ return;
127
+ }
128
+ isWhopPaymentSubmitted.current = true;
129
+ setError(null);
130
+ try {
131
+ const result = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/whop-prepare-order`, {
132
+ method: 'POST',
133
+ body: JSON.stringify({ checkoutSessionId, storeId }),
134
+ headers: { 'Content-Type': 'application/json' },
135
+ });
136
+ if (!result) {
137
+ isWhopPaymentSubmitted.current = false;
138
+ setError('Failed to prepare order.');
139
+ onPaymentFailed?.('Failed to prepare order.');
140
+ return;
141
+ }
142
+ orderIdRef.current = result.orderId;
143
+ whopRef.current.submit();
144
+ }
145
+ catch (err) {
146
+ console.error('Error preparing Whop order:', err);
147
+ isWhopPaymentSubmitted.current = false;
148
+ setError(err?.message || 'Failed to process payment.');
149
+ onPaymentFailed?.(err?.message || 'Failed to process payment.');
150
+ }
151
+ };
152
+ // Whop onComplete callback
153
+ const handleWhopPaymentCompleted = async (receiptId) => {
154
+ isWhopPaymentSubmitted.current = false;
155
+ if (!receiptId) {
156
+ setError('No receipt ID received from Whop.');
157
+ onPaymentFailed?.('No receipt ID received from Whop.');
158
+ return;
159
+ }
160
+ if (!orderIdRef.current) {
161
+ setError('No order ID found.');
162
+ onPaymentFailed?.('No order ID found.');
163
+ return;
164
+ }
165
+ // Update order with latest checkout session data
166
+ try {
167
+ const updatedOrder = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/update-order`, {
168
+ method: 'POST',
169
+ body: JSON.stringify({ checkoutSessionId, orderId: orderIdRef.current }),
170
+ headers: { 'Content-Type': 'application/json' },
171
+ });
172
+ if (!updatedOrder?.id) {
173
+ setError('Error updating order.');
174
+ onPaymentFailed?.('Error updating order.');
175
+ return;
176
+ }
177
+ setIsPaymentLoading(true);
178
+ startPolling(receiptId, updatedOrder.id);
179
+ }
180
+ catch (err) {
181
+ console.error('Error updating order:', err);
182
+ setError('Error finalizing order.');
183
+ onPaymentFailed?.('Error finalizing order.');
184
+ }
185
+ };
186
+ // Expose submit method to parent
187
+ useImperativeHandle(ref, () => ({
188
+ submit: handleWhopPayment,
189
+ }));
190
+ if (isPaymentLoading || isLoadingSession) {
191
+ return (_jsx("div", { className: "rounded-lg border border-[var(--border-color)] bg-[var(--background-color)] p-6", children: _jsxs("div", { className: "py-8 text-center", children: [_jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-[var(--primary-color)]" }), _jsx("p", { className: "mt-4 text-sm text-[var(--text-secondary-color)]", children: planId ? 'Updating checkout...' : 'Initializing checkout...' })] }) }));
192
+ }
193
+ if (!planId) {
194
+ return (_jsx("div", { className: "rounded-lg border border-[var(--border-color)] bg-[var(--background-color)] p-6", children: _jsx("div", { className: "py-8 text-center", children: _jsx("p", { className: "text-sm text-red-600", children: "Failed to initialize checkout. Please try again." }) }) }));
195
+ }
196
+ return (_jsxs(_Fragment, { children: [loading && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: _jsx("div", { className: "rounded-lg bg-white p-8 shadow-xl", children: _jsxs("div", { className: "flex flex-col items-center", children: [_jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-[var(--primary-color)]" }), _jsx("p", { className: "mt-4 text-base font-medium text-gray-900", children: "Payment in progress" })] }) }) })), _jsxs("div", { className: "rounded-lg border border-[var(--border-color)] bg-[var(--background-color)]", children: [_jsx(WhopCheckoutEmbed, { planId: planId, hidePrice: true, ref: whopRef, hideEmail: true, theme: "light", prefill: {
197
+ email: customerEmail || '',
198
+ address: shippingAddress
199
+ ? {
200
+ name: `${shippingAddress.firstName || ''} ${shippingAddress.lastName || ''}`.trim(),
201
+ country: shippingAddress.country || '',
202
+ line1: shippingAddress.address1 || '',
203
+ city: shippingAddress.city || '',
204
+ state: shippingAddress.state || '',
205
+ postalCode: shippingAddress.postal || '',
206
+ }
207
+ : undefined,
208
+ }, hideAddressForm: true, skipRedirect: true, onStateChange: (state) => {
209
+ setError(null);
210
+ if (state === 'ready') {
211
+ isEmbedReady.current = true;
212
+ syncAddressAndEmail();
213
+ onReady?.();
214
+ if (isWhopPaymentSubmitted.current) {
215
+ isWhopPaymentSubmitted.current = false;
216
+ onPaymentFailed?.('Payment was not completed.');
217
+ }
218
+ }
219
+ if (state === 'loading') {
220
+ setLoading(true);
221
+ }
222
+ else {
223
+ setLoading(false);
224
+ }
225
+ }, onComplete: (_planId, receiptId) => handleWhopPaymentCompleted(receiptId), onAddressValidationError: (err) => {
226
+ setError(err.error_message);
227
+ isWhopPaymentSubmitted.current = false;
228
+ onPaymentFailed?.(err.error_message);
229
+ }, hideSubmitButton: true }, planId), error && (_jsx("div", { className: "border-t border-[var(--border-color)] p-4", children: _jsx("div", { className: "rounded-md bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-800", children: error }) }) }))] })] }));
230
+ }));
231
+ WhopCheckout.displayName = 'WhopCheckout';
@@ -51,5 +51,5 @@ export function SimpleContextExample() {
51
51
  // Access data from previous steps
52
52
  const order = funnel.context?.resources?.order;
53
53
  const customer = funnel.context?.resources?.customer;
54
- return (_jsxs("div", { children: [_jsxs("h1", { children: ["Order ", order?.id] }), _jsxs("p", { children: ["Customer: ", customer?.email] }), _jsxs("p", { children: ["Amount: $", ((order?.amount || 0) / 100).toFixed(2)] })] }));
54
+ return (_jsxs("div", { children: [_jsxs("h1", { children: ["Order ", order?.id] }), _jsxs("p", { children: ["Customer: ", String(customer?.email ?? '')] }), _jsxs("p", { children: ["Amount: $", (Number(order?.amount || 0) / 100).toFixed(2)] })] }));
55
55
  }
@@ -0,0 +1,14 @@
1
+ import type { Payment, PaymentOptions } from '../../../core/resources/payments';
2
+ import type { PaymentsResource } from '../../../core/resources/payments';
3
+ import type { UsePaymentOptions } from '../usePaymentQuery';
4
+ interface UseAirwallexRadarActionParams {
5
+ paymentsResource: PaymentsResource;
6
+ startPolling: any;
7
+ setError: (error: string | null) => void;
8
+ setIsLoading: (loading: boolean) => void;
9
+ hookOptionsRef: React.MutableRefObject<UsePaymentOptions | undefined>;
10
+ }
11
+ export declare function useAirwallexRadarAction({ paymentsResource, startPolling, setError, setIsLoading, hookOptionsRef, }: UseAirwallexRadarActionParams): {
12
+ handleAirwallexRadar: (payment: Payment, actionData: any, options: PaymentOptions | undefined, handlePaymentAction: any) => Promise<void>;
13
+ };
14
+ export {};
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Hook for handling Airwallex fraud detection
3
+ */
4
+ import { useCallback } from 'react';
5
+ export function useAirwallexRadarAction({ paymentsResource, startPolling, setError, setIsLoading, hookOptionsRef, }) {
6
+ const handleAirwallexRadar = useCallback(async (payment, actionData, options = {}, handlePaymentAction) => {
7
+ // Handle Airwallex fraud detection - collect device fingerprint
8
+ const isTest = actionData.metadata?.isTest || false;
9
+ const orderId = payment.order?.id;
10
+ const checkoutSessionId = payment.order?.checkoutSessionId;
11
+ if (!orderId || !checkoutSessionId) {
12
+ console.error('Airwallex radar: missing order or checkoutSessionId');
13
+ setError('Missing order information for fraud detection');
14
+ setIsLoading(false);
15
+ options.onFailure?.('Missing order information for fraud detection');
16
+ options.onPaymentFailed?.({
17
+ code: 'AIRWALLEX_RADAR_MISSING_ORDER',
18
+ message: 'Missing order information for fraud detection',
19
+ payment,
20
+ });
21
+ return;
22
+ }
23
+ try {
24
+ // Generate a unique session ID for Airwallex device fingerprinting
25
+ const generateAirwallexSessionId = () => {
26
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
27
+ const r = (Math.random() * 16) | 0;
28
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
29
+ return v.toString(16);
30
+ });
31
+ };
32
+ const sessionId = generateAirwallexSessionId();
33
+ // Load Airwallex fraud API script if not already loaded
34
+ const existingScript = document.getElementById('airwallex-fraud-api');
35
+ if (!existingScript) {
36
+ const script = document.createElement('script');
37
+ script.type = 'text/javascript';
38
+ script.async = true;
39
+ script.id = 'airwallex-fraud-api';
40
+ script.setAttribute('data-order-session-id', sessionId);
41
+ // Use demo URL for testing, production URL for live
42
+ const baseUrl = isTest ? 'https://static-demo.airwallex.com' : 'https://static.airwallex.com';
43
+ script.src = `${baseUrl}/webapp/fraud/device-fingerprint/index.js`;
44
+ // Wait for script to load
45
+ await new Promise((resolve, reject) => {
46
+ script.onload = () => {
47
+ console.log('Airwallex fraud API script loaded successfully');
48
+ resolve();
49
+ };
50
+ script.onerror = () => {
51
+ reject(new Error('Failed to load Airwallex fraud API script'));
52
+ };
53
+ // Append script to body
54
+ document.body.appendChild(script);
55
+ });
56
+ }
57
+ else {
58
+ // Update existing script with new session ID
59
+ existingScript.setAttribute('data-order-session-id', sessionId);
60
+ console.log('Updated existing Airwallex script with new session ID');
61
+ }
62
+ console.log('Airwallex device fingerprint session created:', sessionId);
63
+ // Save radar session to database
64
+ await paymentsResource.saveRadarSession({
65
+ checkoutSessionId,
66
+ orderId,
67
+ airwallexRadarSessionId: sessionId,
68
+ });
69
+ console.log('Airwallex radar session saved to database');
70
+ // Resume payment by calling completePaymentAfterAction
71
+ const resumedPayment = await paymentsResource.completePaymentAfterAction(payment.id);
72
+ console.log('Payment resumed after Airwallex radar:', resumedPayment);
73
+ console.log('📊 [usePayment] Resumed payment details:', {
74
+ status: resumedPayment.status,
75
+ requireAction: resumedPayment.requireAction,
76
+ hasRequireActionData: !!resumedPayment.requireActionData,
77
+ error: resumedPayment.error,
78
+ });
79
+ // Handle the resumed payment response
80
+ // IMPORTANT: Check for declined/failed status FIRST, before checking requireAction
81
+ // This is because a failed 3DS can return with status=declined but still have requireAction set
82
+ if (resumedPayment.status === 'declined' || resumedPayment.status === 'failed') {
83
+ // Payment declined or failed - extract error message from response
84
+ const errorMsg = resumedPayment.error?.message
85
+ || resumedPayment.error?.processorMessage
86
+ || 'Payment declined';
87
+ console.error('❌ [usePayment] Payment declined after Airwallex radar:', errorMsg);
88
+ setError(errorMsg);
89
+ setIsLoading(false);
90
+ options.onFailure?.(errorMsg);
91
+ options.onPaymentFailed?.({
92
+ code: resumedPayment.error?.code || 'PAYMENT_DECLINED',
93
+ message: errorMsg,
94
+ payment: resumedPayment,
95
+ });
96
+ // Hook-level callback (universal handler)
97
+ if (hookOptionsRef.current?.onPaymentFailed) {
98
+ hookOptionsRef.current.onPaymentFailed(errorMsg, {
99
+ isRedirectReturn: false,
100
+ });
101
+ }
102
+ }
103
+ else if (resumedPayment.status === 'succeeded') {
104
+ // Payment succeeded
105
+ setIsLoading(false);
106
+ const response = {
107
+ paymentId: resumedPayment.id,
108
+ payment: resumedPayment,
109
+ order: resumedPayment.order,
110
+ };
111
+ // Hook-level callback (universal handler)
112
+ if (hookOptionsRef.current?.onPaymentCompleted) {
113
+ await hookOptionsRef.current.onPaymentCompleted(resumedPayment, {
114
+ isRedirectReturn: false,
115
+ order: response.order,
116
+ });
117
+ }
118
+ options.onSuccess?.(response);
119
+ options.onPaymentSuccess?.(response);
120
+ }
121
+ else if (resumedPayment.requireAction !== 'none' && resumedPayment.requireActionData) {
122
+ // Payment requires another action (e.g., 3DS)
123
+ await handlePaymentAction(resumedPayment, options);
124
+ }
125
+ else {
126
+ // Start polling for final status
127
+ startPolling(resumedPayment.id, {
128
+ onRequireAction: (updatedPayment) => {
129
+ void handlePaymentAction(updatedPayment, options);
130
+ },
131
+ onSuccess: async (successPayment) => {
132
+ setIsLoading(false);
133
+ const response = {
134
+ paymentId: successPayment.id,
135
+ payment: successPayment,
136
+ order: successPayment.order,
137
+ };
138
+ // Hook-level callback (universal handler)
139
+ if (hookOptionsRef.current?.onPaymentCompleted) {
140
+ await hookOptionsRef.current.onPaymentCompleted(successPayment, {
141
+ isRedirectReturn: false,
142
+ order: response.order,
143
+ });
144
+ }
145
+ options.onSuccess?.(response);
146
+ options.onPaymentSuccess?.(response);
147
+ },
148
+ onFailure: async (errorMsg) => {
149
+ setError(errorMsg);
150
+ setIsLoading(false);
151
+ // Hook-level callback (universal handler)
152
+ if (hookOptionsRef.current?.onPaymentFailed) {
153
+ await hookOptionsRef.current.onPaymentFailed(errorMsg, {
154
+ isRedirectReturn: false,
155
+ });
156
+ }
157
+ options.onFailure?.(errorMsg);
158
+ options.onPaymentFailed?.({
159
+ code: 'PAYMENT_FAILED',
160
+ message: errorMsg,
161
+ payment,
162
+ });
163
+ },
164
+ });
165
+ }
166
+ }
167
+ catch (radarError) {
168
+ const errorMsg = radarError instanceof Error ? radarError.message : 'Airwallex fraud detection failed';
169
+ console.error('Airwallex radar error:', radarError);
170
+ setError(errorMsg);
171
+ setIsLoading(false);
172
+ options.onFailure?.(errorMsg);
173
+ options.onPaymentFailed?.({
174
+ code: 'AIRWALLEX_RADAR_FAILED',
175
+ message: errorMsg,
176
+ payment,
177
+ });
178
+ }
179
+ }, [paymentsResource, startPolling, setError, setIsLoading, hookOptionsRef]);
180
+ return { handleAirwallexRadar };
181
+ }
@@ -0,0 +1,9 @@
1
+ import type { Payment, PaymentOptions } from '../../../core/resources/payments';
2
+ interface UseErrorActionParams {
3
+ setError: (error: string | null) => void;
4
+ setIsLoading: (loading: boolean) => void;
5
+ }
6
+ export declare function useErrorAction({ setError, setIsLoading }: UseErrorActionParams): {
7
+ handleError: (payment: Payment, actionData: any, options?: PaymentOptions) => Promise<void>;
8
+ };
9
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Hook for handling error actions
3
+ */
4
+ import { useCallback } from 'react';
5
+ export function useErrorAction({ setError, setIsLoading }) {
6
+ const handleError = useCallback(async (payment, actionData, options = {}) => {
7
+ const errorMsg = actionData.message || 'Payment processing failed';
8
+ const errorCode = actionData.errorCode || 'PAYMENT_FAILED';
9
+ setError(errorMsg);
10
+ setIsLoading(false);
11
+ // Legacy callback (backwards compatibility)
12
+ options.onFailure?.(errorMsg);
13
+ // Funnel-aligned callback (recommended)
14
+ options.onPaymentFailed?.({
15
+ code: errorCode,
16
+ message: errorMsg,
17
+ payment,
18
+ });
19
+ }, [setError, setIsLoading]);
20
+ return { handleError };
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { Payment, PaymentOptions } from '../../../core/resources/payments';
2
+ import type { PaymentsResource } from '../../../core/resources/payments';
3
+ import type { UsePaymentOptions } from '../usePaymentQuery';
4
+ interface UseFinixRadarActionParams {
5
+ paymentsResource: PaymentsResource;
6
+ startPolling: any;
7
+ setError: (error: string | null) => void;
8
+ setIsLoading: (loading: boolean) => void;
9
+ hookOptionsRef: React.MutableRefObject<UsePaymentOptions | undefined>;
10
+ }
11
+ export declare function useFinixRadarAction({ paymentsResource, startPolling, setError, setIsLoading, hookOptionsRef, }: UseFinixRadarActionParams): {
12
+ handleFinixRadar: (payment: Payment, actionData: any, options: PaymentOptions | undefined, handlePaymentAction: any) => Promise<void>;
13
+ };
14
+ export {};