@tagadapay/plugin-sdk 3.1.12 → 3.1.24
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/build-cdn.js +397 -11
- package/dist/data/iso3166.d.ts +23 -33
- package/dist/data/iso3166.js +134 -198
- package/dist/data/languages.d.ts +5 -64
- package/dist/data/languages.js +23 -143
- package/dist/external-tracker.js +623 -3426
- package/dist/external-tracker.min.js +2 -25
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +14 -4
- package/dist/react/config/payment.js +47 -9
- package/dist/react/hooks/useCheckout.d.ts +3 -0
- package/dist/react/hooks/useCheckout.js +4 -1
- package/dist/react/hooks/useISOData.js +1 -1
- package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/react/hooks/usePluginConfig.js +9 -10
- package/dist/react/providers/TagadaProvider.js +1 -1
- package/dist/tagada-react-sdk-minimal.min.js +36 -0
- package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
- package/dist/tagada-react-sdk.js +37821 -0
- package/dist/tagada-react-sdk.min.js +78 -0
- package/dist/tagada-react-sdk.min.js.map +7 -0
- package/dist/tagada-sdk.js +16044 -0
- package/dist/tagada-sdk.min.js +32 -0
- package/dist/tagada-sdk.min.js.map +7 -0
- package/dist/v2/cdn-react-minimal.d.ts +23 -0
- package/dist/v2/cdn-react-minimal.js +26 -0
- package/dist/v2/core/client.d.ts +4 -2
- package/dist/v2/core/client.js +5 -4
- package/dist/v2/core/config/environment.js +2 -1
- package/dist/v2/core/errors.d.ts +75 -0
- package/dist/v2/core/errors.js +104 -0
- package/dist/v2/core/funnelClient.d.ts +100 -10
- package/dist/v2/core/funnelClient.js +121 -27
- package/dist/v2/core/isoData.d.ts +4 -4
- package/dist/v2/core/isoData.js +7 -7
- package/dist/v2/core/pixelMapping.d.ts +49 -0
- package/dist/v2/core/pixelMapping.js +363 -0
- package/dist/v2/core/resources/apiClient.d.ts +2 -0
- package/dist/v2/core/resources/apiClient.js +52 -9
- package/dist/v2/core/resources/checkout.d.ts +99 -30
- package/dist/v2/core/resources/checkout.js +14 -0
- package/dist/v2/core/resources/customer.d.ts +20 -19
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
- package/dist/v2/core/resources/funnel.d.ts +17 -17
- package/dist/v2/core/resources/payments.d.ts +89 -13
- package/dist/v2/core/resources/payments.js +27 -9
- package/dist/v2/core/resources/postPurchases.d.ts +17 -0
- package/dist/v2/core/resources/postPurchases.js +20 -0
- package/dist/v2/core/types.d.ts +50 -12
- package/dist/v2/core/types.js +0 -3
- package/dist/v2/core/utils/checkout.d.ts +2 -2
- package/dist/v2/core/utils/checkout.js +7 -2
- package/dist/v2/core/utils/currency.d.ts +14 -0
- package/dist/v2/core/utils/currency.js +40 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +0 -10
- package/dist/v2/core/utils/deviceInfo.js +152 -76
- package/dist/v2/core/utils/index.d.ts +1 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/order.d.ts +13 -9
- package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
- package/dist/v2/core/utils/pluginConfig.js +36 -12
- package/dist/v2/index.d.ts +6 -3
- package/dist/v2/index.js +4 -2
- package/dist/v2/react/components/FunnelScriptInjector.js +166 -77
- package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
- package/dist/v2/react/components/StripeExpressButton.js +171 -0
- package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
- package/dist/v2/react/components/WhopCheckout.js +237 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +31 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
- package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +16 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +63 -10
- package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
- package/dist/v2/react/hooks/useFunnel.js +8 -4
- package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
- package/dist/v2/react/hooks/useGeoLocation.js +4 -2
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
- package/dist/v2/react/hooks/useISOData.d.ts +2 -5
- package/dist/v2/react/hooks/useISOData.js +26 -27
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
- package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
- package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
- package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
- package/dist/v2/react/hooks/usePixelTracking.js +283 -504
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
- package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
- package/dist/v2/react/hooks/useRemappableParams.js +23 -23
- package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
- package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
- package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
- package/dist/v2/react/hooks/useStepConfig.js +14 -7
- package/dist/v2/react/hooks/useTranslation.js +23 -8
- package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
- package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
- package/dist/v2/react/index.d.ts +15 -1
- package/dist/v2/react/index.js +7 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +3 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +12 -2
- package/dist/v2/react/providers/TagadaProvider.js +74 -5
- package/dist/v2/standalone/external-tracker.d.ts +52 -46
- package/dist/v2/standalone/external-tracker.js +205 -98
- package/dist/v2/standalone/index.d.ts +40 -0
- package/dist/v2/standalone/index.js +148 -1
- package/dist/v2/standalone/payment-service.d.ts +134 -0
- package/dist/v2/standalone/payment-service.js +928 -0
- package/package.json +6 -4
- package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
- package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
- package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
- package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
if (!planId) {
|
|
132
|
+
isWhopPaymentSubmitted.current = false;
|
|
133
|
+
setError('No plan available for payment.');
|
|
134
|
+
onPaymentFailed?.('No plan available for payment.');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const result = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/whop-prepare-order`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
body: JSON.stringify({ checkoutSessionId, storeId, planId }),
|
|
140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
});
|
|
142
|
+
if (!result) {
|
|
143
|
+
isWhopPaymentSubmitted.current = false;
|
|
144
|
+
setError('Failed to prepare order.');
|
|
145
|
+
onPaymentFailed?.('Failed to prepare order.');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
orderIdRef.current = result.orderId;
|
|
149
|
+
whopRef.current.submit();
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.error('Error preparing Whop order:', err);
|
|
153
|
+
isWhopPaymentSubmitted.current = false;
|
|
154
|
+
setError(err?.message || 'Failed to process payment.');
|
|
155
|
+
onPaymentFailed?.(err?.message || 'Failed to process payment.');
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
// Whop onComplete callback
|
|
159
|
+
const handleWhopPaymentCompleted = async (receiptId) => {
|
|
160
|
+
isWhopPaymentSubmitted.current = false;
|
|
161
|
+
if (!receiptId) {
|
|
162
|
+
setError('No receipt ID received from Whop.');
|
|
163
|
+
onPaymentFailed?.('No receipt ID received from Whop.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!orderIdRef.current) {
|
|
167
|
+
setError('No order ID found.');
|
|
168
|
+
onPaymentFailed?.('No order ID found.');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Update order with latest checkout session data
|
|
172
|
+
try {
|
|
173
|
+
const updatedOrder = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/update-order`, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
body: JSON.stringify({ checkoutSessionId, orderId: orderIdRef.current }),
|
|
176
|
+
headers: { 'Content-Type': 'application/json' },
|
|
177
|
+
});
|
|
178
|
+
if (!updatedOrder?.id) {
|
|
179
|
+
setError('Error updating order.');
|
|
180
|
+
onPaymentFailed?.('Error updating order.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
setIsPaymentLoading(true);
|
|
184
|
+
startPolling(receiptId, updatedOrder.id);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error('Error updating order:', err);
|
|
188
|
+
setError('Error finalizing order.');
|
|
189
|
+
onPaymentFailed?.('Error finalizing order.');
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
// Expose submit method to parent
|
|
193
|
+
useImperativeHandle(ref, () => ({
|
|
194
|
+
submit: handleWhopPayment,
|
|
195
|
+
}));
|
|
196
|
+
if (isPaymentLoading || isLoadingSession) {
|
|
197
|
+
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...' })] }) }));
|
|
198
|
+
}
|
|
199
|
+
if (!planId) {
|
|
200
|
+
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." }) }) }));
|
|
201
|
+
}
|
|
202
|
+
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: {
|
|
203
|
+
email: customerEmail || '',
|
|
204
|
+
address: shippingAddress
|
|
205
|
+
? {
|
|
206
|
+
name: `${shippingAddress.firstName || ''} ${shippingAddress.lastName || ''}`.trim(),
|
|
207
|
+
country: shippingAddress.country || '',
|
|
208
|
+
line1: shippingAddress.address1 || '',
|
|
209
|
+
city: shippingAddress.city || '',
|
|
210
|
+
state: shippingAddress.state || '',
|
|
211
|
+
postalCode: shippingAddress.postal || '',
|
|
212
|
+
}
|
|
213
|
+
: undefined,
|
|
214
|
+
}, hideAddressForm: true, skipRedirect: true, onStateChange: (state) => {
|
|
215
|
+
setError(null);
|
|
216
|
+
if (state === 'ready') {
|
|
217
|
+
isEmbedReady.current = true;
|
|
218
|
+
syncAddressAndEmail();
|
|
219
|
+
onReady?.();
|
|
220
|
+
if (isWhopPaymentSubmitted.current) {
|
|
221
|
+
isWhopPaymentSubmitted.current = false;
|
|
222
|
+
onPaymentFailed?.('Payment was not completed.');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (state === 'loading') {
|
|
226
|
+
setLoading(true);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
setLoading(false);
|
|
230
|
+
}
|
|
231
|
+
}, onComplete: (_planId, receiptId) => handleWhopPaymentCompleted(receiptId), onAddressValidationError: (err) => {
|
|
232
|
+
setError(err.error_message);
|
|
233
|
+
isWhopPaymentSubmitted.current = false;
|
|
234
|
+
onPaymentFailed?.(err.error_message);
|
|
235
|
+
}, 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 }) }) }))] })] }));
|
|
236
|
+
}));
|
|
237
|
+
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 {};
|