@b3dotfun/sdk 0.1.68-alpha.3 → 0.1.68-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/anyspend/platform/client.d.ts +35 -0
- package/dist/cjs/anyspend/platform/client.js +158 -0
- package/dist/cjs/anyspend/platform/errors.d.ts +38 -0
- package/dist/cjs/anyspend/platform/errors.js +77 -0
- package/dist/cjs/anyspend/platform/index.d.ts +87 -0
- package/dist/cjs/anyspend/platform/index.js +85 -0
- package/dist/cjs/anyspend/platform/resources/analytics.d.ts +7 -0
- package/dist/cjs/anyspend/platform/resources/analytics.js +12 -0
- package/dist/cjs/anyspend/platform/resources/checkout-sessions.d.ts +17 -0
- package/dist/cjs/anyspend/platform/resources/checkout-sessions.js +27 -0
- package/dist/cjs/anyspend/platform/resources/customers.d.ts +19 -0
- package/dist/cjs/anyspend/platform/resources/customers.js +34 -0
- package/dist/cjs/anyspend/platform/resources/discount-codes.d.ts +29 -0
- package/dist/cjs/anyspend/platform/resources/discount-codes.js +31 -0
- package/dist/cjs/anyspend/platform/resources/events.d.ts +14 -0
- package/dist/cjs/anyspend/platform/resources/events.js +16 -0
- package/dist/cjs/anyspend/platform/resources/notifications.d.ts +18 -0
- package/dist/cjs/anyspend/platform/resources/notifications.js +27 -0
- package/dist/cjs/anyspend/platform/resources/organization.d.ts +17 -0
- package/dist/cjs/anyspend/platform/resources/organization.js +15 -0
- package/dist/cjs/anyspend/platform/resources/payment-links.d.ts +21 -0
- package/dist/cjs/anyspend/platform/resources/payment-links.js +49 -0
- package/dist/cjs/anyspend/platform/resources/products.d.ts +27 -0
- package/dist/cjs/anyspend/platform/resources/products.js +31 -0
- package/dist/cjs/anyspend/platform/resources/transactions.d.ts +11 -0
- package/dist/cjs/anyspend/platform/resources/transactions.js +25 -0
- package/dist/cjs/anyspend/platform/resources/webhooks.d.ts +14 -0
- package/dist/cjs/anyspend/platform/resources/webhooks.js +33 -0
- package/dist/cjs/anyspend/platform/resources/widgets.d.ts +38 -0
- package/dist/cjs/anyspend/platform/resources/widgets.js +31 -0
- package/dist/cjs/anyspend/platform/types.d.ts +478 -0
- package/dist/cjs/anyspend/platform/types.js +5 -0
- package/dist/cjs/anyspend/platform/utils/idempotency.d.ts +4 -0
- package/dist/cjs/anyspend/platform/utils/idempotency.js +17 -0
- package/dist/cjs/anyspend/platform/utils/pagination.d.ts +12 -0
- package/dist/cjs/anyspend/platform/utils/pagination.js +22 -0
- package/dist/cjs/anyspend/react/components/AnySpend.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/AnySpend.js +128 -11
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.js +13 -12
- package/dist/cjs/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/cjs/anyspend/react/components/checkout/KycGate.js +181 -0
- package/dist/cjs/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/cjs/anyspend/react/hooks/index.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +9 -0
- package/dist/cjs/anyspend/react/hooks/useKycStatus.d.ts +42 -0
- package/dist/cjs/anyspend/react/hooks/useKycStatus.js +113 -0
- package/dist/cjs/anyspend/services/anyspend.d.ts +3 -1
- package/dist/cjs/anyspend/services/anyspend.js +2 -1
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +1 -1
- package/dist/cjs/global-account/react/components/ManageAccount/BottomNavigation.js +3 -3
- package/dist/cjs/global-account/react/hooks/useAuth.js +1 -1
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +4 -0
- package/dist/cjs/global-account/react/stores/useModalStore.js +2 -0
- package/dist/cjs/global-account/react/utils/createWagmiConfig.d.ts +18 -0
- package/dist/cjs/global-account/react/utils/createWagmiConfig.js +17 -0
- package/dist/esm/anyspend/platform/client.d.ts +35 -0
- package/dist/esm/anyspend/platform/client.js +153 -0
- package/dist/esm/anyspend/platform/errors.d.ts +38 -0
- package/dist/esm/anyspend/platform/errors.js +67 -0
- package/dist/esm/anyspend/platform/index.d.ts +87 -0
- package/dist/esm/anyspend/platform/index.js +75 -0
- package/dist/esm/anyspend/platform/resources/analytics.d.ts +7 -0
- package/dist/esm/anyspend/platform/resources/analytics.js +8 -0
- package/dist/esm/anyspend/platform/resources/checkout-sessions.d.ts +17 -0
- package/dist/esm/anyspend/platform/resources/checkout-sessions.js +23 -0
- package/dist/esm/anyspend/platform/resources/customers.d.ts +19 -0
- package/dist/esm/anyspend/platform/resources/customers.js +30 -0
- package/dist/esm/anyspend/platform/resources/discount-codes.d.ts +29 -0
- package/dist/esm/anyspend/platform/resources/discount-codes.js +27 -0
- package/dist/esm/anyspend/platform/resources/events.d.ts +14 -0
- package/dist/esm/anyspend/platform/resources/events.js +12 -0
- package/dist/esm/anyspend/platform/resources/notifications.d.ts +18 -0
- package/dist/esm/anyspend/platform/resources/notifications.js +23 -0
- package/dist/esm/anyspend/platform/resources/organization.d.ts +17 -0
- package/dist/esm/anyspend/platform/resources/organization.js +11 -0
- package/dist/esm/anyspend/platform/resources/payment-links.d.ts +21 -0
- package/dist/esm/anyspend/platform/resources/payment-links.js +45 -0
- package/dist/esm/anyspend/platform/resources/products.d.ts +27 -0
- package/dist/esm/anyspend/platform/resources/products.js +27 -0
- package/dist/esm/anyspend/platform/resources/transactions.d.ts +11 -0
- package/dist/esm/anyspend/platform/resources/transactions.js +21 -0
- package/dist/esm/anyspend/platform/resources/webhooks.d.ts +14 -0
- package/dist/esm/anyspend/platform/resources/webhooks.js +29 -0
- package/dist/esm/anyspend/platform/resources/widgets.d.ts +38 -0
- package/dist/esm/anyspend/platform/resources/widgets.js +27 -0
- package/dist/esm/anyspend/platform/types.d.ts +478 -0
- package/dist/esm/anyspend/platform/types.js +4 -0
- package/dist/esm/anyspend/platform/utils/idempotency.d.ts +4 -0
- package/dist/esm/anyspend/platform/utils/idempotency.js +14 -0
- package/dist/esm/anyspend/platform/utils/pagination.d.ts +12 -0
- package/dist/esm/anyspend/platform/utils/pagination.js +19 -0
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +3 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +129 -12
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.js +13 -12
- package/dist/esm/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/esm/anyspend/react/components/checkout/KycGate.js +145 -0
- package/dist/esm/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/esm/anyspend/react/hooks/index.js +1 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +9 -0
- package/dist/esm/anyspend/react/hooks/useKycStatus.d.ts +42 -0
- package/dist/esm/anyspend/react/hooks/useKycStatus.js +107 -0
- package/dist/esm/anyspend/services/anyspend.d.ts +3 -1
- package/dist/esm/anyspend/services/anyspend.js +2 -1
- package/dist/esm/global-account/react/components/B3DynamicModal.js +1 -1
- package/dist/esm/global-account/react/components/ManageAccount/BottomNavigation.js +3 -3
- package/dist/esm/global-account/react/hooks/useAuth.js +2 -2
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +4 -0
- package/dist/esm/global-account/react/stores/useModalStore.js +2 -0
- package/dist/esm/global-account/react/utils/createWagmiConfig.d.ts +18 -0
- package/dist/esm/global-account/react/utils/createWagmiConfig.js +16 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/platform/client.d.ts +35 -0
- package/dist/types/anyspend/platform/errors.d.ts +38 -0
- package/dist/types/anyspend/platform/index.d.ts +87 -0
- package/dist/types/anyspend/platform/resources/analytics.d.ts +7 -0
- package/dist/types/anyspend/platform/resources/checkout-sessions.d.ts +17 -0
- package/dist/types/anyspend/platform/resources/customers.d.ts +19 -0
- package/dist/types/anyspend/platform/resources/discount-codes.d.ts +29 -0
- package/dist/types/anyspend/platform/resources/events.d.ts +14 -0
- package/dist/types/anyspend/platform/resources/notifications.d.ts +18 -0
- package/dist/types/anyspend/platform/resources/organization.d.ts +17 -0
- package/dist/types/anyspend/platform/resources/payment-links.d.ts +21 -0
- package/dist/types/anyspend/platform/resources/products.d.ts +27 -0
- package/dist/types/anyspend/platform/resources/transactions.d.ts +11 -0
- package/dist/types/anyspend/platform/resources/webhooks.d.ts +14 -0
- package/dist/types/anyspend/platform/resources/widgets.d.ts +38 -0
- package/dist/types/anyspend/platform/types.d.ts +478 -0
- package/dist/types/anyspend/platform/utils/idempotency.d.ts +4 -0
- package/dist/types/anyspend/platform/utils/pagination.d.ts +12 -0
- package/dist/types/anyspend/react/components/AnySpend.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/types/anyspend/react/hooks/useKycStatus.d.ts +42 -0
- package/dist/types/anyspend/services/anyspend.d.ts +3 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +4 -0
- package/dist/types/global-account/react/utils/createWagmiConfig.d.ts +18 -0
- package/package.json +7 -1
- package/src/anyspend/platform/client.ts +198 -0
- package/src/anyspend/platform/errors.ts +92 -0
- package/src/anyspend/platform/index.ts +129 -0
- package/src/anyspend/platform/resources/analytics.ts +10 -0
- package/src/anyspend/platform/resources/checkout-sessions.ts +36 -0
- package/src/anyspend/platform/resources/customers.ts +54 -0
- package/src/anyspend/platform/resources/discount-codes.ts +63 -0
- package/src/anyspend/platform/resources/events.ts +22 -0
- package/src/anyspend/platform/resources/notifications.ts +37 -0
- package/src/anyspend/platform/resources/organization.ts +24 -0
- package/src/anyspend/platform/resources/payment-links.ts +74 -0
- package/src/anyspend/platform/resources/products.ts +59 -0
- package/src/anyspend/platform/resources/transactions.ts +33 -0
- package/src/anyspend/platform/resources/webhooks.ts +47 -0
- package/src/anyspend/platform/resources/widgets.ts +63 -0
- package/src/anyspend/platform/types.ts +532 -0
- package/src/anyspend/platform/utils/idempotency.ts +15 -0
- package/src/anyspend/platform/utils/pagination.ts +32 -0
- package/src/anyspend/react/components/AnySpend.tsx +150 -13
- package/src/anyspend/react/components/checkout/FiatCheckoutPanel.tsx +16 -13
- package/src/anyspend/react/components/checkout/KycGate.tsx +351 -0
- package/src/anyspend/react/hooks/index.ts +1 -0
- package/src/anyspend/react/hooks/useAnyspendCreateOnrampOrder.ts +10 -0
- package/src/anyspend/react/hooks/useKycStatus.ts +140 -0
- package/src/anyspend/services/anyspend.ts +4 -0
- package/src/global-account/react/components/B3DynamicModal.tsx +0 -2
- package/src/global-account/react/components/ManageAccount/BottomNavigation.tsx +7 -7
- package/src/global-account/react/hooks/useAuth.ts +2 -2
- package/src/global-account/react/stores/useModalStore.ts +6 -0
- package/src/global-account/react/utils/createWagmiConfig.tsx +18 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { eqci, getChainName, getDefaultToken, getExplorerTxUrl, getHyperliquidUSDCToken, HYPERLIQUID_CHAIN_ID, HYPERLIQUID_USDC_ADDRESS, USDC_BASE, ZERO_ADDRESS, } from "../../../anyspend/index.js";
|
|
4
4
|
import { useAnyspendCreateOnrampOrder, useAnyspendCreateOrder, useAnyspendOrderAndTransactions, useAnyspendQuote, useGasPrice, useGeoOnrampOptions, } from "../../../anyspend/react/index.js";
|
|
5
|
-
import { Button, ShinyButton, StyleRoot, TabsPrimitive, toast, TransitionPanel, useAccountWallet, useB3Config, useModalStore, useProfile, useRouter, useSearchParamsSSR, useTokenBalanceDirect, useTokenData, useTokenFromUrl, } from "../../../global-account/react/index.js";
|
|
5
|
+
import { Button, ShinyButton, StyleRoot, TabsPrimitive, toast, TransitionPanel, useAccountWallet, useAuth, useAuthStore, useB3Config, useModalStore, useProfile, useRouter, useSearchParamsSSR, useTokenBalanceDirect, useTokenData, useTokenFromUrl, } from "../../../global-account/react/index.js";
|
|
6
6
|
import BottomNavigation from "../../../global-account/react/components/ManageAccount/BottomNavigation.js";
|
|
7
7
|
import { useAccountWalletImage } from "../../../global-account/react/hooks/useAccountWallet.js";
|
|
8
8
|
import { getThirdwebChain } from "../../../shared/constants/chains/supported.js";
|
|
@@ -30,6 +30,9 @@ import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaym
|
|
|
30
30
|
import { GasIndicator } from "./common/GasIndicator.js";
|
|
31
31
|
import { OrderDetails, OrderDetailsLoadingView } from "./common/OrderDetails.js";
|
|
32
32
|
import { OrderHistory } from "./common/OrderHistory.js";
|
|
33
|
+
import { KycGate } from "./checkout/KycGate.js";
|
|
34
|
+
import { useWalletAuthHeaders } from "../hooks/useKycStatus.js";
|
|
35
|
+
import { LoginStep } from "../../../global-account/react/components/SignInWithB3/steps/LoginStep.js";
|
|
33
36
|
import { PanelOnramp } from "./common/PanelOnramp.js";
|
|
34
37
|
import { PanelOnrampPayment } from "./common/PanelOnrampPayment.js";
|
|
35
38
|
import { PointsDetailPanel } from "./common/PointsDetailPanel.js";
|
|
@@ -50,6 +53,8 @@ export var PanelView;
|
|
|
50
53
|
PanelView[PanelView["POINTS_DETAIL"] = 8] = "POINTS_DETAIL";
|
|
51
54
|
PanelView[PanelView["FEE_DETAIL"] = 9] = "FEE_DETAIL";
|
|
52
55
|
PanelView[PanelView["DIRECT_TRANSFER_SUCCESS"] = 10] = "DIRECT_TRANSFER_SUCCESS";
|
|
56
|
+
PanelView[PanelView["FIAT_KYC"] = 11] = "FIAT_KYC";
|
|
57
|
+
PanelView[PanelView["FIAT_AUTH"] = 12] = "FIAT_AUTH";
|
|
53
58
|
})(PanelView || (PanelView = {}));
|
|
54
59
|
const ANYSPEND_RECIPIENTS_KEY = "anyspend_recipients";
|
|
55
60
|
export function AnySpend(props) {
|
|
@@ -63,6 +68,16 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
63
68
|
const { partnerId } = useB3Config();
|
|
64
69
|
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
65
70
|
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
71
|
+
const { isAuthenticated } = useAuth();
|
|
72
|
+
// KYC approval is tracked per-session so we only prompt the wallet
|
|
73
|
+
// signature when the user actually clicks Buy, not on panel mount.
|
|
74
|
+
// useRef so handleFiatOrder can read the updated value synchronously
|
|
75
|
+
// in the same frame that onStatusResolved sets it (setState is async).
|
|
76
|
+
const kycApprovedRef = useRef(false);
|
|
77
|
+
// Pre-warm wallet auth headers inside user-gesture context (button click)
|
|
78
|
+
// so the signing prompt fires before we navigate away — browsers block
|
|
79
|
+
// wallet popups triggered from async/non-gesture contexts (React Query queryFn).
|
|
80
|
+
const { getHeaders: getKycHeaders } = useWalletAuthHeaders();
|
|
66
81
|
// Determine if we're in "buy mode" based on whether destination token props are provided
|
|
67
82
|
const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId);
|
|
68
83
|
// Add refs to track URL state
|
|
@@ -72,7 +87,17 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
72
87
|
const animationDirection = useRef(null);
|
|
73
88
|
// Track previous panel for proper back navigation
|
|
74
89
|
const previousPanel = useRef(PanelView.MAIN);
|
|
75
|
-
const [activeTab, setActiveTab] = useState(
|
|
90
|
+
const [activeTab, setActiveTab] = useState(() => {
|
|
91
|
+
if (typeof window !== "undefined") {
|
|
92
|
+
const stored = sessionStorage.getItem("anyspend_active_tab");
|
|
93
|
+
if (stored === "crypto" || stored === "fiat")
|
|
94
|
+
return stored;
|
|
95
|
+
}
|
|
96
|
+
return defaultActiveTab;
|
|
97
|
+
});
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
sessionStorage.setItem("anyspend_active_tab", activeTab);
|
|
100
|
+
}, [activeTab]);
|
|
76
101
|
const [orderId, setOrderId] = useState(loadOrder);
|
|
77
102
|
const [directTransferTxHash, setDirectTransferTxHash] = useState();
|
|
78
103
|
const { orderAndTransactions: oat, getOrderAndTransactionsError } = useAnyspendOrderAndTransactions(orderId);
|
|
@@ -93,9 +118,19 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
93
118
|
const [customRecipients, setCustomRecipients] = useState([]);
|
|
94
119
|
// Payment method state with dual-state system (auto + explicit user selection)
|
|
95
120
|
const { cryptoPaymentMethod, setCryptoPaymentMethod, selectedCryptoPaymentMethod, setSelectedCryptoPaymentMethod, effectiveCryptoPaymentMethod, resetPaymentMethods, } = useCryptoPaymentMethodState();
|
|
96
|
-
const [selectedFiatPaymentMethod, setSelectedFiatPaymentMethod] = useState(
|
|
121
|
+
const [selectedFiatPaymentMethod, setSelectedFiatPaymentMethod] = useState(() => {
|
|
122
|
+
if (typeof window !== "undefined") {
|
|
123
|
+
const stored = sessionStorage.getItem("anyspend_fiat_method");
|
|
124
|
+
if (stored && Object.values(FiatPaymentMethod).includes(stored))
|
|
125
|
+
return stored;
|
|
126
|
+
}
|
|
127
|
+
return FiatPaymentMethod.NONE;
|
|
128
|
+
});
|
|
97
129
|
// const [newRecipientAddress, setNewRecipientAddress] = useState("");
|
|
98
130
|
// const recipientInputRef = useRef<HTMLInputElement>(null);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
sessionStorage.setItem("anyspend_fiat_method", selectedFiatPaymentMethod);
|
|
133
|
+
}, [selectedFiatPaymentMethod]);
|
|
99
134
|
// Get initial chain IDs from URL or defaults
|
|
100
135
|
const initialSrcChainId = sourceChainId || parseInt(searchParams.get("fromChainId") || "0") || mainnet.id;
|
|
101
136
|
const initialDstChainId = parseInt(searchParams.get("toChainId") || "0") || (isBuyMode ? destinationTokenChainId : base.id);
|
|
@@ -112,10 +147,27 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
112
147
|
const [selectedSrcToken, setSelectedSrcToken] = useState(effectiveSrcToken);
|
|
113
148
|
const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
114
149
|
const [srcAmount, setSrcAmount] = useState(searchParams.get("fromAmount") || "0");
|
|
115
|
-
// State for onramp amount
|
|
116
|
-
const [srcAmountOnRamp, setSrcAmountOnRamp] = useState(
|
|
117
|
-
|
|
118
|
-
|
|
150
|
+
// State for onramp amount — persisted in sessionStorage so it survives Persona KYC roundtrip
|
|
151
|
+
const [srcAmountOnRamp, setSrcAmountOnRamp] = useState(() => {
|
|
152
|
+
if (typeof window !== "undefined") {
|
|
153
|
+
const stored = sessionStorage.getItem("anyspend_fiat_amount");
|
|
154
|
+
if (stored)
|
|
155
|
+
return stored;
|
|
156
|
+
}
|
|
157
|
+
return searchParams.get("fromAmount") || "5";
|
|
158
|
+
});
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
sessionStorage.setItem("anyspend_fiat_amount", srcAmountOnRamp);
|
|
161
|
+
}, [srcAmountOnRamp]);
|
|
162
|
+
// State for destination chain/token selection (sync effects come after state declarations below) — persisted in sessionStorage for Persona KYC roundtrip
|
|
163
|
+
const [selectedDstChainId, setSelectedDstChainId] = useState(() => {
|
|
164
|
+
if (!isBuyMode && typeof window !== "undefined") {
|
|
165
|
+
const stored = sessionStorage.getItem("anyspend_dst_chain_id");
|
|
166
|
+
if (stored)
|
|
167
|
+
return parseInt(stored, 10);
|
|
168
|
+
}
|
|
169
|
+
return initialDstChainId;
|
|
170
|
+
});
|
|
119
171
|
// Helper to check if address is Hyperliquid USDC (supports both 34-char and 42-char formats)
|
|
120
172
|
const isHyperliquidUSDCAddress = (address) => eqci(address, HYPERLIQUID_USDC_ADDRESS) || eqci(address, ZERO_ADDRESS);
|
|
121
173
|
const defaultDstToken = isBuyMode
|
|
@@ -135,9 +187,29 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
135
187
|
defaultToken: defaultDstToken,
|
|
136
188
|
prefix: "to",
|
|
137
189
|
});
|
|
138
|
-
const [selectedDstToken, setSelectedDstToken] = useState(
|
|
190
|
+
const [selectedDstToken, setSelectedDstToken] = useState(() => {
|
|
191
|
+
if (!isBuyMode && typeof window !== "undefined") {
|
|
192
|
+
const stored = sessionStorage.getItem("anyspend_dst_token");
|
|
193
|
+
if (stored) {
|
|
194
|
+
try {
|
|
195
|
+
return JSON.parse(stored);
|
|
196
|
+
}
|
|
197
|
+
catch { }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return isBuyMode ? defaultDstToken : dstTokenFromUrl;
|
|
201
|
+
});
|
|
139
202
|
const { data: dstTokenMetadata } = useTokenData(selectedDstToken?.chainId, selectedDstToken?.address);
|
|
140
203
|
const [dstAmount, setDstAmount] = useState(searchParams.get("toAmount") || "");
|
|
204
|
+
// Sync dst chain/token to sessionStorage so they survive Persona KYC roundtrip
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!isBuyMode)
|
|
207
|
+
sessionStorage.setItem("anyspend_dst_chain_id", selectedDstChainId.toString());
|
|
208
|
+
}, [selectedDstChainId, isBuyMode]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!isBuyMode)
|
|
211
|
+
sessionStorage.setItem("anyspend_dst_token", JSON.stringify(selectedDstToken));
|
|
212
|
+
}, [selectedDstToken, isBuyMode]);
|
|
141
213
|
const [isSrcInputDirty, setIsSrcInputDirty] = useState(true);
|
|
142
214
|
// Add refs to track if we've applied metadata
|
|
143
215
|
const appliedSrcMetadataRef = useRef(false);
|
|
@@ -490,6 +562,16 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
490
562
|
}, [anyspendQuote, isSrcInputDirty, destinationTokenAmount]);
|
|
491
563
|
// Call onSuccess when order is executed
|
|
492
564
|
useOnOrderSuccess({ orderData: oat, orderId, onSuccess });
|
|
565
|
+
// Clear all persisted selection state once an order is submitted — next open starts fresh
|
|
566
|
+
useEffect(() => {
|
|
567
|
+
if (orderId) {
|
|
568
|
+
sessionStorage.removeItem("anyspend_fiat_amount");
|
|
569
|
+
sessionStorage.removeItem("anyspend_active_tab");
|
|
570
|
+
sessionStorage.removeItem("anyspend_fiat_method");
|
|
571
|
+
sessionStorage.removeItem("anyspend_dst_chain_id");
|
|
572
|
+
sessionStorage.removeItem("anyspend_dst_token");
|
|
573
|
+
}
|
|
574
|
+
}, [orderId]);
|
|
493
575
|
const { createOrder, isCreatingOrder } = useAnyspendCreateOrder({
|
|
494
576
|
onSuccess: data => {
|
|
495
577
|
const orderId = data.data.id;
|
|
@@ -570,8 +652,8 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
570
652
|
if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
|
|
571
653
|
return { text: "Select payment method", disable: false, error: false, loading: false };
|
|
572
654
|
}
|
|
573
|
-
// If payment method is selected, show "
|
|
574
|
-
return { text: "
|
|
655
|
+
// If payment method is selected, show "Continue"
|
|
656
|
+
return { text: "Continue", disable: false, error: false, loading: false };
|
|
575
657
|
}
|
|
576
658
|
if (activeTab === "crypto") {
|
|
577
659
|
// For crypto: check payment method first, then recipient
|
|
@@ -592,7 +674,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
592
674
|
return { text: "Continue to payment", disable: false, error: false, loading: false };
|
|
593
675
|
}
|
|
594
676
|
}
|
|
595
|
-
return { text: "
|
|
677
|
+
return { text: "Continue", disable: false, error: false, loading: false };
|
|
596
678
|
}, [
|
|
597
679
|
activeInputAmountInWei,
|
|
598
680
|
isSameChainSameToken,
|
|
@@ -754,7 +836,22 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
754
836
|
paymentMethodString = "";
|
|
755
837
|
}
|
|
756
838
|
else if (paymentMethod === FiatPaymentMethod.STRIPE_WEB2) {
|
|
757
|
-
// Stripe embedded payment form
|
|
839
|
+
// Stripe embedded payment form requires authentication + KYC
|
|
840
|
+
// Read from store directly to avoid stale closure when called from onLoginSuccess callback
|
|
841
|
+
const currentlyAuthenticated = useAuthStore.getState().isAuthenticated;
|
|
842
|
+
if (!currentlyAuthenticated) {
|
|
843
|
+
navigateToPanel(PanelView.FIAT_AUTH, "forward");
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (!kycApprovedRef.current) {
|
|
847
|
+
// Pre-sign the KYC auth message NOW (user-gesture context) so the
|
|
848
|
+
// result is cached before useKycStatus fires its queryFn inside the
|
|
849
|
+
// FIAT_KYC panel. Wallets/browsers block signing prompts from async
|
|
850
|
+
// (non-gesture) contexts, which is exactly what React Query uses.
|
|
851
|
+
await getKycHeaders().catch(() => { });
|
|
852
|
+
navigateToPanel(PanelView.FIAT_KYC, "forward");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
758
855
|
if (!stripeWeb2Support.isSupport) {
|
|
759
856
|
toast.error("Pay with Card not available");
|
|
760
857
|
return;
|
|
@@ -863,6 +960,14 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
863
960
|
window.removeEventListener("popstate", handlePopState);
|
|
864
961
|
};
|
|
865
962
|
}, [activePanel, navigateBack]);
|
|
963
|
+
// When auth completes while on the FIAT_AUTH panel, navigate back and retry the order
|
|
964
|
+
useEffect(() => {
|
|
965
|
+
if (isAuthenticated && activePanel === PanelView.FIAT_AUTH) {
|
|
966
|
+
navigateBack();
|
|
967
|
+
handleFiatOrder(selectedFiatPaymentMethod);
|
|
968
|
+
}
|
|
969
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
970
|
+
}, [isAuthenticated]);
|
|
866
971
|
const historyView = (_jsx("div", { className: "mx-auto flex w-[560px] max-w-full flex-col items-center", children: _jsx(OrderHistory, { mode: mode, onBack: navigateBack, onSelectOrder: onSelectOrder }) }));
|
|
867
972
|
const orderDetailsView = (_jsx("div", { className: "mx-auto w-[460px] max-w-full p-5", children: _jsx("div", { className: "relative flex flex-col gap-4", children: oat && (_jsx(OrderDetails, { mode: mode, order: oat.data.order, depositTxs: oat.data.depositTxs, relayTxs: oat.data.relayTxs, executeTx: oat.data.executeTx, refundTxs: oat.data.refundTxs, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onPaymentMethodChange: method => {
|
|
868
973
|
// When user explicitly changes payment method, set it as selected
|
|
@@ -997,6 +1102,16 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
997
1102
|
setDirectTransferTxHash(undefined);
|
|
998
1103
|
setB3ModalOpen(false);
|
|
999
1104
|
}, className: "bg-as-brand hover:bg-as-brand/90 w-full text-white", children: resolvedReturnLabel || "Done" })) })] }));
|
|
1105
|
+
const kycView = (_jsx("div", { className: "mx-auto flex w-full max-w-[460px] flex-col gap-4 px-5 pt-5", children: _jsx(KycGate, { enabled: activePanel === PanelView.FIAT_KYC, onStatusResolved: approved => {
|
|
1106
|
+
if (approved) {
|
|
1107
|
+
kycApprovedRef.current = true;
|
|
1108
|
+
navigateBack();
|
|
1109
|
+
handleFiatOrder(selectedFiatPaymentMethod);
|
|
1110
|
+
}
|
|
1111
|
+
} }) }));
|
|
1112
|
+
const authView = (_jsx("div", { className: "mx-auto w-full max-w-[460px]", children: _jsx(LoginStep, { chain: baseChain, onSuccess: async () => {
|
|
1113
|
+
// isAuthenticated will be true at this point — the useEffect below handles navigation
|
|
1114
|
+
} }) }));
|
|
1000
1115
|
// Add tabs to the main component when no order is loaded
|
|
1001
1116
|
return (_jsx(StyleRoot, { children: _jsx("div", { className: classes?.container ||
|
|
1002
1117
|
cn("anyspend-container font-inter mx-auto w-full max-w-[460px]", mode === "page" &&
|
|
@@ -1030,5 +1145,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
1030
1145
|
_jsx("div", { className: cn(mode === "page" && "p-6"), children: pointsDetailView }, "points-detail-view"),
|
|
1031
1146
|
_jsx("div", { className: cn(mode === "page" && "p-6"), children: feeDetailView }, "fee-detail-view"),
|
|
1032
1147
|
_jsx("div", { className: cn(mode === "page" && "p-6"), children: directTransferSuccessView }, "direct-transfer-success-view"),
|
|
1148
|
+
_jsx("div", { className: cn(mode === "page" && "p-6"), children: kycView }, "fiat-kyc-view"),
|
|
1149
|
+
_jsx("div", { className: cn(mode === "page" && "p-6"), children: authView }, "fiat-auth-view"),
|
|
1033
1150
|
] }) }) }));
|
|
1034
1151
|
}
|
|
@@ -10,6 +10,7 @@ import { AddressElement, Elements, PaymentElement, useElements, useStripe } from
|
|
|
10
10
|
import { Loader2, Lock } from "lucide-react";
|
|
11
11
|
import { AnimatePresence, motion } from "motion/react";
|
|
12
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
13
|
+
import { KycGate } from "./KycGate.js";
|
|
13
14
|
export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinationTokenChainId, totalAmount, themeColor, onSuccess, onOrderCreated, onError, callbackMetadata, classes, feeOnTop, }) {
|
|
14
15
|
// Stable refs for callback props to avoid re-triggering effects
|
|
15
16
|
const onErrorRef = useRef(onError);
|
|
@@ -48,18 +49,12 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
48
49
|
const raw = formatUnits(anyspendQuote.data.currencyIn.amount, USDC_BASE.decimals);
|
|
49
50
|
return parseFloat(raw).toFixed(2);
|
|
50
51
|
}, [isStablecoin, formattedAmount, anyspendQuote]);
|
|
51
|
-
// Debug: log computed values for Stripe flow diagnostics
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
console.log("@@fiat-checkout:debug", {
|
|
54
|
-
totalAmount,
|
|
55
|
-
formattedAmount,
|
|
56
|
-
isStablecoin,
|
|
57
|
-
isLoadingAnyspendQuote,
|
|
58
|
-
quoteAmount: anyspendQuote?.data?.currencyIn?.amount,
|
|
59
|
-
usdAmount,
|
|
60
|
-
});
|
|
61
|
-
}, [totalAmount, formattedAmount, isStablecoin, isLoadingAnyspendQuote, anyspendQuote, usdAmount]);
|
|
62
52
|
const { geoData, stripeOnrampSupport, stripeWeb2Support, isLoading: isLoadingGeo, } = useGeoOnrampOptions(usdAmount || "0");
|
|
53
|
+
// KYC state
|
|
54
|
+
const [kycApproved, setKycApproved] = useState(false);
|
|
55
|
+
const handleKycResolved = useCallback((approved) => {
|
|
56
|
+
setKycApproved(approved);
|
|
57
|
+
}, []);
|
|
63
58
|
// Order state
|
|
64
59
|
const [orderId, setOrderId] = useState(null);
|
|
65
60
|
const [stripePaymentIntentId, setStripePaymentIntentId] = useState(null);
|
|
@@ -82,13 +77,14 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
82
77
|
onErrorRef.current?.(error);
|
|
83
78
|
},
|
|
84
79
|
});
|
|
85
|
-
// Auto-create onramp order when Stripe Web2 is supported and all data is ready
|
|
80
|
+
// Auto-create onramp order when Stripe Web2 is supported, KYC approved, and all data is ready
|
|
86
81
|
useEffect(() => {
|
|
87
82
|
if (!isLoadingGeo &&
|
|
88
83
|
(!isStablecoin ? !isLoadingAnyspendQuote : true) &&
|
|
89
84
|
usdAmount &&
|
|
90
85
|
parseFloat(usdAmount) > 0 &&
|
|
91
86
|
stripeWeb2Support?.isSupport &&
|
|
87
|
+
kycApproved &&
|
|
92
88
|
!orderCreatedRef.current &&
|
|
93
89
|
!orderId &&
|
|
94
90
|
!isCreatingOrder &&
|
|
@@ -129,6 +125,7 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
129
125
|
isLoadingAnyspendQuote,
|
|
130
126
|
usdAmount,
|
|
131
127
|
stripeWeb2Support,
|
|
128
|
+
kycApproved,
|
|
132
129
|
orderId,
|
|
133
130
|
isCreatingOrder,
|
|
134
131
|
orderError,
|
|
@@ -152,6 +149,10 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
152
149
|
if (!hasStripeWeb2 && !hasStripeRedirect) {
|
|
153
150
|
return (_jsx(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-fiat-unavailable py-4 text-center", classes?.fiatPanel), children: _jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Card payments are not available in your region for this amount." }) }));
|
|
154
151
|
}
|
|
152
|
+
// KYC gate — shown before order creation when verification is needed
|
|
153
|
+
if (!kycApproved) {
|
|
154
|
+
return _jsx(KycGate, { themeColor: themeColor, classes: classes, enabled: true, onStatusResolved: handleKycResolved });
|
|
155
|
+
}
|
|
155
156
|
// Order creation error - show with retry
|
|
156
157
|
if (orderError) {
|
|
157
158
|
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-fiat-error flex flex-col items-center gap-3 py-4", classes?.fiatPanel), children: [_jsx("p", { className: "text-sm text-red-500", children: orderError }), _jsx("button", { onClick: () => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
2
|
+
interface KycGateProps {
|
|
3
|
+
themeColor?: string;
|
|
4
|
+
classes?: AnySpendCheckoutClasses;
|
|
5
|
+
/** Only fetch KYC status (and prompt wallet signature) when true. */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
/** Called when KYC status is resolved (approved or not required) */
|
|
8
|
+
onStatusResolved: (approved: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function KycGate({ themeColor, classes, enabled, onStatusResolved }: KycGateProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../../../../shared/utils/cn.js";
|
|
4
|
+
import { ShinyButton, TextShimmer, useAuth, useB3Config, useModalStore } from "../../../../global-account/react/index.js";
|
|
5
|
+
import { thirdwebB3Chain } from "../../../../shared/constants/chains/b3Chain.js";
|
|
6
|
+
import { Loader2, ShieldCheck, AlertTriangle, Clock } from "lucide-react";
|
|
7
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
|
+
import { useCreateKycInquiry, useKycStatus, useVerifyKyc } from "../../hooks/useKycStatus.js";
|
|
10
|
+
export function KycGate({ themeColor, classes, enabled = false, onStatusResolved }) {
|
|
11
|
+
const { isAuthenticated, isAuthenticating } = useAuth();
|
|
12
|
+
const { kycStatus, isLoadingKycStatus, refetchKycStatus } = useKycStatus(enabled);
|
|
13
|
+
const { createInquiry, isCreatingInquiry } = useCreateKycInquiry();
|
|
14
|
+
const { verifyKyc, isVerifying } = useVerifyKyc();
|
|
15
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
16
|
+
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
17
|
+
const { partnerId } = useB3Config();
|
|
18
|
+
const [personaOpen, setPersonaOpen] = useState(false);
|
|
19
|
+
const [personaError, setPersonaError] = useState(null);
|
|
20
|
+
const [personaCancelled, setPersonaCancelled] = useState(false);
|
|
21
|
+
const prevStatusRef = useRef(null);
|
|
22
|
+
// Notify parent when status resolves
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!kycStatus)
|
|
25
|
+
return;
|
|
26
|
+
const currentStatus = kycStatus.status;
|
|
27
|
+
if (currentStatus === prevStatusRef.current)
|
|
28
|
+
return;
|
|
29
|
+
prevStatusRef.current = currentStatus;
|
|
30
|
+
if (!kycStatus.kycRequired || currentStatus === "approved") {
|
|
31
|
+
onStatusResolved(true);
|
|
32
|
+
}
|
|
33
|
+
}, [kycStatus, onStatusResolved]);
|
|
34
|
+
const openPersonaFlow = useCallback(async (config) => {
|
|
35
|
+
setPersonaOpen(true);
|
|
36
|
+
try {
|
|
37
|
+
// Dynamic import to keep bundle small
|
|
38
|
+
const { Client } = await import("persona");
|
|
39
|
+
const client = new Client({
|
|
40
|
+
inquiryId: config.inquiryId,
|
|
41
|
+
sessionToken: config.sessionToken,
|
|
42
|
+
environment: config.environment === "production" ? "production" : "sandbox",
|
|
43
|
+
onComplete: async ({ inquiryId }) => {
|
|
44
|
+
// Reopen the modal first so the user lands back in the checkout flow
|
|
45
|
+
setB3ModalOpen(true);
|
|
46
|
+
setPersonaOpen(false);
|
|
47
|
+
if (inquiryId) {
|
|
48
|
+
try {
|
|
49
|
+
const result = await verifyKyc(inquiryId);
|
|
50
|
+
if (result.status === "approved") {
|
|
51
|
+
onStatusResolved(true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
setPersonaError(err instanceof Error ? err.message : "Verification check failed — please retry.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
refetchKycStatus();
|
|
59
|
+
},
|
|
60
|
+
onCancel: () => {
|
|
61
|
+
// Reopen the modal so the user can retry or cancel the purchase
|
|
62
|
+
setB3ModalOpen(true);
|
|
63
|
+
setPersonaOpen(false);
|
|
64
|
+
setPersonaCancelled(true);
|
|
65
|
+
},
|
|
66
|
+
onError: error => {
|
|
67
|
+
// Reopen the modal so the user sees the error and can retry
|
|
68
|
+
setB3ModalOpen(true);
|
|
69
|
+
setPersonaOpen(false);
|
|
70
|
+
setPersonaError(error?.message || "Verification encountered an error");
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
// Close the modal before opening Persona so its overlay is fully
|
|
74
|
+
// interactive — no Radix Dialog backdrop or z-index conflicts.
|
|
75
|
+
// The modal's contentType is preserved in Zustand and restored on reopen.
|
|
76
|
+
setB3ModalOpen(false);
|
|
77
|
+
client.open();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
setPersonaOpen(false);
|
|
81
|
+
setB3ModalOpen(true);
|
|
82
|
+
setPersonaError("Failed to load verification module");
|
|
83
|
+
}
|
|
84
|
+
}, [verifyKyc, onStatusResolved, refetchKycStatus, setB3ModalOpen]);
|
|
85
|
+
const handleStartVerification = useCallback(async () => {
|
|
86
|
+
setPersonaError(null);
|
|
87
|
+
setPersonaCancelled(false);
|
|
88
|
+
try {
|
|
89
|
+
const { inquiryId, sessionToken } = await createInquiry();
|
|
90
|
+
openPersonaFlow({
|
|
91
|
+
inquiryId,
|
|
92
|
+
sessionToken,
|
|
93
|
+
environment: kycStatus?.config?.environment,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
setPersonaError(error instanceof Error ? error.message : "Failed to start verification");
|
|
98
|
+
}
|
|
99
|
+
}, [createInquiry, kycStatus, openPersonaFlow]);
|
|
100
|
+
const handleSignIn = useCallback(() => {
|
|
101
|
+
setB3ModalContentType({ type: "signInWithB3", showBackButton: false, chain: thirdwebB3Chain, partnerId });
|
|
102
|
+
setB3ModalOpen(true);
|
|
103
|
+
}, [setB3ModalContentType, setB3ModalOpen, partnerId]);
|
|
104
|
+
const handleResumeVerification = useCallback(() => {
|
|
105
|
+
if (!kycStatus?.inquiry)
|
|
106
|
+
return;
|
|
107
|
+
setPersonaError(null);
|
|
108
|
+
setPersonaCancelled(false);
|
|
109
|
+
openPersonaFlow({
|
|
110
|
+
inquiryId: kycStatus.inquiry.inquiryId,
|
|
111
|
+
sessionToken: kycStatus.inquiry.sessionToken,
|
|
112
|
+
environment: kycStatus.config?.environment,
|
|
113
|
+
});
|
|
114
|
+
}, [kycStatus, openPersonaFlow]);
|
|
115
|
+
// Auth loading state
|
|
116
|
+
if (isAuthenticating) {
|
|
117
|
+
return (_jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.2, ease: "easeOut" }, className: cn("anyspend-kyc-loading flex flex-col items-center gap-3 py-6", classes?.fiatPanel), children: [_jsx(Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }), _jsx(TextShimmer, { duration: 1.5, className: "text-sm", children: "Checking authentication..." })] }));
|
|
118
|
+
}
|
|
119
|
+
// Not authenticated — prompt to login
|
|
120
|
+
if (!isAuthenticated) {
|
|
121
|
+
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-kyc-auth flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [_jsx(ShieldCheck, { className: "h-8 w-8 text-gray-400" }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Login required to pay with card" }), _jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "Sign in to your B3 account to complete identity verification." })] }), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleSignIn, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx(ShieldCheck, { className: "h-4 w-4" }), "Sign In"] }) })] }));
|
|
122
|
+
}
|
|
123
|
+
// Loading KYC status state
|
|
124
|
+
if (isLoadingKycStatus) {
|
|
125
|
+
return (_jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.2, ease: "easeOut" }, className: cn("anyspend-kyc-loading flex flex-col items-center gap-3 py-6", classes?.fiatPanel), children: [_jsx(Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }), _jsx(TextShimmer, { duration: 1.5, className: "text-sm", children: "Checking verification status..." })] }));
|
|
126
|
+
}
|
|
127
|
+
// Not required or already approved — render nothing
|
|
128
|
+
if (!kycStatus?.kycRequired || kycStatus.status === "approved") {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Persona flow is open - show loading
|
|
132
|
+
if (personaOpen) {
|
|
133
|
+
return (_jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.2, ease: "easeOut" }, className: cn("anyspend-kyc-persona flex flex-col items-center gap-3 py-6", classes?.fiatPanel), children: [_jsx(Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }), _jsx(TextShimmer, { duration: 1.5, className: "text-sm", children: "Identity verification in progress..." }), _jsx("p", { className: "text-xs text-gray-400 dark:text-gray-500", children: "Complete the verification in the popup window" })] }));
|
|
134
|
+
}
|
|
135
|
+
// Needs review or completed (submitted, awaiting Persona decision)
|
|
136
|
+
if (kycStatus.status === "needs_review" || kycStatus.status === "completed") {
|
|
137
|
+
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-kyc-review flex flex-col items-center gap-3 py-2", classes?.fiatPanel), children: [_jsx(Clock, { className: "h-8 w-8 text-amber-500" }), _jsx("p", { className: "text-center text-sm font-medium text-amber-700 dark:text-amber-300", children: "Your verification is under review" }), _jsx("p", { className: "text-center text-xs text-amber-600 dark:text-amber-400", children: "This usually takes a few minutes. Please check back shortly." }), _jsx("button", { onClick: () => refetchKycStatus(), className: "mt-1 text-sm font-medium text-amber-700 underline hover:text-amber-800 dark:text-amber-300", children: "Check status" })] }));
|
|
138
|
+
}
|
|
139
|
+
// Pending (started before) - offer resume
|
|
140
|
+
if (kycStatus.status === "pending" && kycStatus.inquiry) {
|
|
141
|
+
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-kyc-resume flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [_jsx(ShieldCheck, { className: "h-8 w-8 text-blue-500" }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Continue verification" }), _jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "You have an incomplete verification. Resume to continue." })] }), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleResumeVerification, disabled: isCreatingInquiry, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [isCreatingInquiry ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : _jsx(ShieldCheck, { className: "h-4 w-4" }), "Resume Verification"] }) })] }));
|
|
142
|
+
}
|
|
143
|
+
// Not verified / declined / expired - show verification prompt
|
|
144
|
+
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-kyc-prompt flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [_jsx(ShieldCheck, { className: "h-8 w-8 text-blue-500" }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Identity verification required" }), _jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "Card payments require a one-time identity check. This takes about 2 minutes." })] }), _jsxs(AnimatePresence, { initial: false, children: [personaError && (_jsx(motion.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.2, ease: "easeOut" }, style: { overflow: "hidden" }, className: "w-full rounded-lg border border-red-200 bg-red-50 px-3 py-2 dark:border-red-800 dark:bg-red-900/20", children: _jsx("p", { className: "text-center text-sm text-red-600 dark:text-red-400", children: personaError }) }, "kyc-error")), personaCancelled && (_jsx(motion.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.2, ease: "easeOut" }, style: { overflow: "hidden" }, className: "w-full rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 dark:border-amber-800 dark:bg-amber-900/20", children: _jsx("p", { className: "text-center text-sm text-amber-600 dark:text-amber-400", children: "Verification cancelled. Click below to try again." }) }, "kyc-cancelled"))] }), kycStatus.status === "declined" && (_jsxs("div", { className: "flex items-center gap-1.5 rounded-lg border border-red-200 bg-red-50 px-3 py-2 dark:border-red-800 dark:bg-red-900/20", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5 text-red-500" }), _jsx("p", { className: "text-xs text-red-600 dark:text-red-400", children: "Previous verification was declined. You may try again." })] })), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleStartVerification, disabled: isCreatingInquiry || isVerifying, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [isCreatingInquiry ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : _jsx(ShieldCheck, { className: "h-4 w-4" }), isCreatingInquiry ? "Starting..." : "Verify Identity"] }) })] }));
|
|
145
|
+
}
|
|
@@ -7,6 +7,7 @@ import { useMutation } from "@tanstack/react-query";
|
|
|
7
7
|
import { useMemo } from "react";
|
|
8
8
|
import { parseUnits } from "viem";
|
|
9
9
|
import { base } from "viem/chains";
|
|
10
|
+
import { useWalletAuthHeaders } from "./useKycStatus.js";
|
|
10
11
|
import { useValidatedClientReferenceId } from "./useValidatedClientReferenceId.js";
|
|
11
12
|
/**
|
|
12
13
|
* Hook for creating onramp orders in the Anyspend protocol
|
|
@@ -15,6 +16,7 @@ import { useValidatedClientReferenceId } from "./useValidatedClientReferenceId.j
|
|
|
15
16
|
export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
16
17
|
// Get B3 context values
|
|
17
18
|
const { partnerId } = useB3Config();
|
|
19
|
+
const { getHeaders: getWalletAuthHeaders } = useWalletAuthHeaders();
|
|
18
20
|
// Get validated client reference ID from B3 context
|
|
19
21
|
const createValidatedClientReferenceId = useValidatedClientReferenceId();
|
|
20
22
|
// Get fingerprint data
|
|
@@ -36,6 +38,12 @@ export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
|
36
38
|
const srcChain = base.id;
|
|
37
39
|
// Create order with USDC on Base as source
|
|
38
40
|
const srcAmountOnRampInWei = parseUnits(srcFiatAmount, USDC_BASE.decimals);
|
|
41
|
+
// For card payments, include wallet auth headers so the backend can verify
|
|
42
|
+
// KYC by the signing wallet address (may differ from the B3 JWT address).
|
|
43
|
+
let kycWalletHeaders;
|
|
44
|
+
if (onramp.vendor === "stripe-web2") {
|
|
45
|
+
kycWalletHeaders = await getWalletAuthHeaders().catch(() => undefined);
|
|
46
|
+
}
|
|
39
47
|
return await anyspendService.createOrder({
|
|
40
48
|
recipientAddress: normalizeAddress(recipientAddress),
|
|
41
49
|
type: orderType,
|
|
@@ -73,6 +81,7 @@ export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
|
73
81
|
visitorData,
|
|
74
82
|
callbackMetadata: params.callbackMetadata,
|
|
75
83
|
feeOnTop: params.feeOnTop,
|
|
84
|
+
kycWalletHeaders,
|
|
76
85
|
});
|
|
77
86
|
}
|
|
78
87
|
catch (error) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface KycStatusResponse {
|
|
2
|
+
kycRequired: boolean;
|
|
3
|
+
status: "not_verified" | "pending" | "completed" | "approved" | "declined" | "needs_review" | "expired";
|
|
4
|
+
inquiry?: {
|
|
5
|
+
inquiryId: string;
|
|
6
|
+
sessionToken: string;
|
|
7
|
+
};
|
|
8
|
+
config?: {
|
|
9
|
+
templateId: string;
|
|
10
|
+
environment: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface KycInquiryResponse {
|
|
14
|
+
inquiryId: string;
|
|
15
|
+
sessionToken: string;
|
|
16
|
+
}
|
|
17
|
+
interface KycVerifyResponse {
|
|
18
|
+
status: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns a function that builds the wallet-signature auth headers.
|
|
22
|
+
* Caches signatures for 4 minutes (server allows 5-minute window).
|
|
23
|
+
*/
|
|
24
|
+
export declare function useWalletAuthHeaders(): {
|
|
25
|
+
address: `0x${string}` | undefined;
|
|
26
|
+
getHeaders: () => Promise<Record<string, string>>;
|
|
27
|
+
};
|
|
28
|
+
export declare function useKycStatus(enabled?: boolean): {
|
|
29
|
+
kycStatus: KycStatusResponse | null;
|
|
30
|
+
isLoadingKycStatus: boolean;
|
|
31
|
+
kycStatusError: Error | null;
|
|
32
|
+
refetchKycStatus: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<KycStatusResponse, Error>>;
|
|
33
|
+
};
|
|
34
|
+
export declare function useCreateKycInquiry(): {
|
|
35
|
+
createInquiry: import("@tanstack/react-query").UseMutateAsyncFunction<KycInquiryResponse, Error, void, unknown>;
|
|
36
|
+
isCreatingInquiry: boolean;
|
|
37
|
+
};
|
|
38
|
+
export declare function useVerifyKyc(): {
|
|
39
|
+
verifyKyc: import("@tanstack/react-query").UseMutateAsyncFunction<KycVerifyResponse, Error, string, unknown>;
|
|
40
|
+
isVerifying: boolean;
|
|
41
|
+
};
|
|
42
|
+
export {};
|