@b3dotfun/sdk 0.1.68-alpha.1 → 0.1.68-alpha.11
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 +5 -1
- package/dist/cjs/anyspend/react/components/AnySpend.js +123 -12
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.d.ts +12 -6
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +55 -8
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +2 -2
- package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.js +2 -2
- package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +1 -1
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.js +46 -16
- package/dist/cjs/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/cjs/anyspend/react/components/checkout/KycGate.js +203 -0
- package/dist/cjs/anyspend/react/components/checkout/VariablePricingInput.d.ts +17 -0
- package/dist/cjs/anyspend/react/components/checkout/VariablePricingInput.js +145 -0
- package/dist/cjs/anyspend/react/components/index.d.ts +1 -1
- 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 +13 -0
- package/dist/cjs/anyspend/react/hooks/useKycStatus.d.ts +47 -0
- package/dist/cjs/anyspend/react/hooks/useKycStatus.js +124 -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/components/SignInWithB3/SignIn.js +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +13 -5
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Privy.js +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStep.js +21 -24
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepCustom.js +1 -1
- package/dist/cjs/global-account/react/hooks/useAuth.js +1 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +3 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +94 -24
- package/dist/cjs/global-account/react/hooks/useGetAllTWSigners.js +2 -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 +5 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +124 -13
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.d.ts +12 -6
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +55 -8
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +2 -2
- package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.js +2 -2
- package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +1 -1
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.js +48 -18
- package/dist/esm/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/esm/anyspend/react/components/checkout/KycGate.js +167 -0
- package/dist/esm/anyspend/react/components/checkout/VariablePricingInput.d.ts +17 -0
- package/dist/esm/anyspend/react/components/checkout/VariablePricingInput.js +142 -0
- package/dist/esm/anyspend/react/components/index.d.ts +1 -1
- 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 +13 -0
- package/dist/esm/anyspend/react/hooks/useKycStatus.d.ts +47 -0
- package/dist/esm/anyspend/react/hooks/useKycStatus.js +117 -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/components/SignInWithB3/SignIn.js +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +13 -5
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Privy.js +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStep.js +21 -24
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepCustom.js +1 -1
- package/dist/esm/global-account/react/hooks/useAuth.js +2 -2
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +3 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.js +94 -24
- package/dist/esm/global-account/react/hooks/useGetAllTWSigners.js +2 -1
- 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 +5 -1
- package/dist/types/anyspend/react/components/checkout/AnySpendCheckout.d.ts +12 -6
- package/dist/types/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/KycGate.d.ts +11 -0
- package/dist/types/anyspend/react/components/checkout/VariablePricingInput.d.ts +17 -0
- package/dist/types/anyspend/react/components/index.d.ts +1 -1
- package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/types/anyspend/react/hooks/useKycStatus.d.ts +47 -0
- package/dist/types/anyspend/services/anyspend.d.ts +3 -1
- package/dist/types/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/types/global-account/react/hooks/useAuthentication.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/docs/checkout-sessions.md +20 -3
- 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 +148 -14
- package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +77 -18
- package/src/anyspend/react/components/checkout/AnySpendCheckoutTrigger.tsx +4 -0
- package/src/anyspend/react/components/checkout/CheckoutPaymentPanel.tsx +4 -0
- package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +4 -13
- package/src/anyspend/react/components/checkout/FiatCheckoutPanel.tsx +81 -17
- package/src/anyspend/react/components/checkout/KycGate.tsx +387 -0
- package/src/anyspend/react/components/checkout/VariablePricingInput.tsx +247 -0
- package/src/anyspend/react/components/index.ts +1 -0
- package/src/anyspend/react/hooks/index.ts +1 -0
- package/src/anyspend/react/hooks/useAnyspendCreateOnrampOrder.ts +14 -0
- package/src/anyspend/react/hooks/useKycStatus.ts +150 -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/components/SignInWithB3/SignIn.tsx +1 -1
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +13 -5
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Privy.tsx +1 -1
- package/src/global-account/react/components/SignInWithB3/steps/LoginStep.tsx +35 -25
- package/src/global-account/react/components/SignInWithB3/steps/LoginStepCustom.tsx +1 -1
- package/src/global-account/react/hooks/useAuth.ts +2 -2
- package/src/global-account/react/hooks/useAuthentication.ts +92 -27
- package/src/global-account/react/hooks/useGetAllTWSigners.tsx +2 -1
- package/src/global-account/react/stores/useModalStore.ts +6 -0
- package/src/global-account/react/utils/createWagmiConfig.tsx +18 -0
|
@@ -5,17 +5,27 @@ import { USDC_BASE } from "../../../../anyspend/constants/index.js";
|
|
|
5
5
|
import { cn } from "../../../../shared/utils/cn.js";
|
|
6
6
|
import { formatUnits } from "../../../../shared/utils/number.js";
|
|
7
7
|
import { getStripePromise } from "../../../../shared/utils/payment.utils.js";
|
|
8
|
-
import { ShinyButton, TextShimmer, useB3Config, useTokenData } from "../../../../global-account/react/index.js";
|
|
8
|
+
import { ShinyButton, TextShimmer, useB3Config, useModalStore, useTokenData } from "../../../../global-account/react/index.js";
|
|
9
|
+
import { thirdwebB3Chain } from "../../../../shared/constants/chains/b3Chain.js";
|
|
9
10
|
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
10
|
-
import { Loader2, Lock } from "lucide-react";
|
|
11
|
+
import { Loader2, Lock, Wallet } from "lucide-react";
|
|
11
12
|
import { AnimatePresence, motion } from "motion/react";
|
|
12
13
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
13
|
-
|
|
14
|
+
import { useAccount } from "wagmi";
|
|
15
|
+
import { KycGate } from "./KycGate.js";
|
|
16
|
+
export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinationTokenChainId, totalAmount, themeColor, onSuccess, onOrderCreated, onError, callbackMetadata, classes, feeOnTop, kycEnabled = false, }) {
|
|
14
17
|
// Stable refs for callback props to avoid re-triggering effects
|
|
15
18
|
const onErrorRef = useRef(onError);
|
|
16
19
|
onErrorRef.current = onError;
|
|
20
|
+
const { address } = useAccount();
|
|
21
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
22
|
+
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
17
23
|
const { data: tokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
|
|
18
|
-
const { theme, stripePublishableKey } = useB3Config();
|
|
24
|
+
const { theme, stripePublishableKey, partnerId } = useB3Config();
|
|
25
|
+
const handleConnectWallet = useCallback(() => {
|
|
26
|
+
setB3ModalContentType({ type: "signInWithB3", showBackButton: false, chain: thirdwebB3Chain, partnerId });
|
|
27
|
+
setB3ModalOpen(true);
|
|
28
|
+
}, [setB3ModalContentType, setB3ModalOpen, partnerId]);
|
|
19
29
|
// Clean decimal string for API calls (no commas, no subscripts)
|
|
20
30
|
const formattedAmount = useMemo(() => {
|
|
21
31
|
const decimals = tokenData?.decimals || 18;
|
|
@@ -48,18 +58,12 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
48
58
|
const raw = formatUnits(anyspendQuote.data.currencyIn.amount, USDC_BASE.decimals);
|
|
49
59
|
return parseFloat(raw).toFixed(2);
|
|
50
60
|
}, [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
61
|
const { geoData, stripeOnrampSupport, stripeWeb2Support, isLoading: isLoadingGeo, } = useGeoOnrampOptions(usdAmount || "0");
|
|
62
|
+
// KYC state — pre-approved when kycEnabled is false (feature flag off)
|
|
63
|
+
const [kycApproved, setKycApproved] = useState(() => !kycEnabled);
|
|
64
|
+
const handleKycResolved = useCallback((approved) => {
|
|
65
|
+
setKycApproved(approved);
|
|
66
|
+
}, []);
|
|
63
67
|
// Order state
|
|
64
68
|
const [orderId, setOrderId] = useState(null);
|
|
65
69
|
const [stripePaymentIntentId, setStripePaymentIntentId] = useState(null);
|
|
@@ -78,17 +82,33 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
78
82
|
}
|
|
79
83
|
},
|
|
80
84
|
onError: (error) => {
|
|
81
|
-
|
|
85
|
+
// Backend requires KYC even when kycEnabled=false — reset to show KycGate
|
|
86
|
+
if (error.message?.includes("KYC verification required")) {
|
|
87
|
+
setKycApproved(false);
|
|
88
|
+
orderCreatedRef.current = false;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
setOrderError(error.message || "Failed to create payment order.");
|
|
92
|
+
}
|
|
82
93
|
onErrorRef.current?.(error);
|
|
83
94
|
},
|
|
84
95
|
});
|
|
85
|
-
//
|
|
96
|
+
// Reset order error when wallet connects so the user gets a clean state
|
|
86
97
|
useEffect(() => {
|
|
87
|
-
if (
|
|
98
|
+
if (address && orderError) {
|
|
99
|
+
setOrderError(null);
|
|
100
|
+
orderCreatedRef.current = false;
|
|
101
|
+
}
|
|
102
|
+
}, [address]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
103
|
+
// Auto-create onramp order when Stripe Web2 is supported, KYC approved, and all data is ready
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (address &&
|
|
106
|
+
!isLoadingGeo &&
|
|
88
107
|
(!isStablecoin ? !isLoadingAnyspendQuote : true) &&
|
|
89
108
|
usdAmount &&
|
|
90
109
|
parseFloat(usdAmount) > 0 &&
|
|
91
110
|
stripeWeb2Support?.isSupport &&
|
|
111
|
+
kycApproved &&
|
|
92
112
|
!orderCreatedRef.current &&
|
|
93
113
|
!orderId &&
|
|
94
114
|
!isCreatingOrder &&
|
|
@@ -124,11 +144,13 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
124
144
|
});
|
|
125
145
|
}
|
|
126
146
|
}, [
|
|
147
|
+
address,
|
|
127
148
|
isLoadingGeo,
|
|
128
149
|
isStablecoin,
|
|
129
150
|
isLoadingAnyspendQuote,
|
|
130
151
|
usdAmount,
|
|
131
152
|
stripeWeb2Support,
|
|
153
|
+
kycApproved,
|
|
132
154
|
orderId,
|
|
133
155
|
isCreatingOrder,
|
|
134
156
|
orderError,
|
|
@@ -152,6 +174,14 @@ export function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, d
|
|
|
152
174
|
if (!hasStripeWeb2 && !hasStripeRedirect) {
|
|
153
175
|
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
176
|
}
|
|
177
|
+
// KYC gate — shown before order creation when verification is needed
|
|
178
|
+
if (!kycApproved) {
|
|
179
|
+
return _jsx(KycGate, { themeColor: themeColor, classes: classes, enabled: true, onStatusResolved: handleKycResolved });
|
|
180
|
+
}
|
|
181
|
+
// No wallet connected — prompt to connect before attempting card payment
|
|
182
|
+
if (!address) {
|
|
183
|
+
return (_jsxs(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("anyspend-fiat-connect flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [_jsx(Wallet, { 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: "Connect wallet to pay with card" }), _jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "A wallet connection is required to complete card payment." })] }), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleConnectWallet, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx(Wallet, { className: "h-4 w-4" }), "Connect Wallet"] }) })] }));
|
|
184
|
+
}
|
|
155
185
|
// Order creation error - show with retry
|
|
156
186
|
if (orderError) {
|
|
157
187
|
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,167 @@
|
|
|
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, useB3Config, useModalStore } from "../../../../global-account/react/index.js";
|
|
5
|
+
import { thirdwebB3Chain } from "../../../../shared/constants/chains/b3Chain.js";
|
|
6
|
+
import { Loader2, ShieldCheck, AlertTriangle, Clock, Wallet } from "lucide-react";
|
|
7
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
|
+
import { useAccount } from "wagmi";
|
|
10
|
+
import { useCreateKycInquiry, useKycStatus, useVerifyKyc, useWalletAuthHeaders } from "../../hooks/useKycStatus.js";
|
|
11
|
+
export function KycGate({ themeColor, classes, enabled = false, onStatusResolved }) {
|
|
12
|
+
const { address } = useAccount();
|
|
13
|
+
const { partnerId } = useB3Config();
|
|
14
|
+
// Gate the status fetch behind explicit user consent so the wallet
|
|
15
|
+
// signature prompt doesn't fire automatically on tab open.
|
|
16
|
+
const [userInitiated, setUserInitiated] = useState(false);
|
|
17
|
+
const { getHeaders: preCacheKycHeaders } = useWalletAuthHeaders();
|
|
18
|
+
const { kycStatus, isLoadingKycStatus, refetchKycStatus } = useKycStatus(enabled && userInitiated);
|
|
19
|
+
const { createInquiry, isCreatingInquiry } = useCreateKycInquiry();
|
|
20
|
+
const { verifyKyc, isVerifying } = useVerifyKyc();
|
|
21
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
22
|
+
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
23
|
+
const [personaOpen, setPersonaOpen] = useState(false);
|
|
24
|
+
const [personaError, setPersonaError] = useState(null);
|
|
25
|
+
const [personaCancelled, setPersonaCancelled] = useState(false);
|
|
26
|
+
const prevStatusRef = useRef(null);
|
|
27
|
+
// Reset consent gate when wallet changes so the signature prompt isn't
|
|
28
|
+
// skipped for a different (or reconnected) wallet with no cached headers.
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setUserInitiated(false);
|
|
31
|
+
}, [address]);
|
|
32
|
+
// Notify parent when status resolves
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!kycStatus)
|
|
35
|
+
return;
|
|
36
|
+
const currentStatus = kycStatus.status;
|
|
37
|
+
if (currentStatus === prevStatusRef.current)
|
|
38
|
+
return;
|
|
39
|
+
prevStatusRef.current = currentStatus;
|
|
40
|
+
if (!kycStatus.kycRequired || currentStatus === "approved") {
|
|
41
|
+
onStatusResolved(true);
|
|
42
|
+
}
|
|
43
|
+
}, [kycStatus, onStatusResolved]);
|
|
44
|
+
const openPersonaFlow = useCallback(async (config) => {
|
|
45
|
+
setPersonaOpen(true);
|
|
46
|
+
try {
|
|
47
|
+
// Dynamic import to keep bundle small
|
|
48
|
+
const { Client } = await import("persona");
|
|
49
|
+
const client = new Client({
|
|
50
|
+
inquiryId: config.inquiryId,
|
|
51
|
+
sessionToken: config.sessionToken,
|
|
52
|
+
environment: config.environment === "production" ? "production" : "sandbox",
|
|
53
|
+
onComplete: async ({ inquiryId }) => {
|
|
54
|
+
// Reopen the modal first so the user lands back in the checkout flow
|
|
55
|
+
setB3ModalOpen(true);
|
|
56
|
+
setPersonaOpen(false);
|
|
57
|
+
if (inquiryId) {
|
|
58
|
+
try {
|
|
59
|
+
const result = await verifyKyc(inquiryId);
|
|
60
|
+
if (result.status === "approved") {
|
|
61
|
+
onStatusResolved(true);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
setPersonaError(err instanceof Error ? err.message : "Verification check failed — please retry.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
refetchKycStatus();
|
|
69
|
+
},
|
|
70
|
+
onCancel: () => {
|
|
71
|
+
// Reopen the modal so the user can retry or cancel the purchase
|
|
72
|
+
setB3ModalOpen(true);
|
|
73
|
+
setPersonaOpen(false);
|
|
74
|
+
setPersonaCancelled(true);
|
|
75
|
+
},
|
|
76
|
+
onError: error => {
|
|
77
|
+
// Reopen the modal so the user sees the error and can retry
|
|
78
|
+
setB3ModalOpen(true);
|
|
79
|
+
setPersonaOpen(false);
|
|
80
|
+
setPersonaError(error?.message || "Verification encountered an error");
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
// Close the modal before opening Persona so its overlay is fully
|
|
84
|
+
// interactive — no Radix Dialog backdrop or z-index conflicts.
|
|
85
|
+
// The modal's contentType is preserved in Zustand and restored on reopen.
|
|
86
|
+
setB3ModalOpen(false);
|
|
87
|
+
client.open();
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
setPersonaOpen(false);
|
|
91
|
+
setB3ModalOpen(true);
|
|
92
|
+
setPersonaError("Failed to load verification module");
|
|
93
|
+
}
|
|
94
|
+
}, [verifyKyc, onStatusResolved, refetchKycStatus, setB3ModalOpen]);
|
|
95
|
+
const handleStartVerification = useCallback(async () => {
|
|
96
|
+
setPersonaError(null);
|
|
97
|
+
setPersonaCancelled(false);
|
|
98
|
+
try {
|
|
99
|
+
const { inquiryId, sessionToken } = await createInquiry();
|
|
100
|
+
openPersonaFlow({
|
|
101
|
+
inquiryId,
|
|
102
|
+
sessionToken,
|
|
103
|
+
environment: kycStatus?.config?.environment,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
setPersonaError(error instanceof Error ? error.message : "Failed to start verification");
|
|
108
|
+
}
|
|
109
|
+
}, [createInquiry, kycStatus, openPersonaFlow]);
|
|
110
|
+
const handleConnectWallet = useCallback(() => {
|
|
111
|
+
setB3ModalContentType({ type: "signInWithB3", showBackButton: false, chain: thirdwebB3Chain, partnerId });
|
|
112
|
+
setB3ModalOpen(true);
|
|
113
|
+
}, [setB3ModalContentType, setB3ModalOpen, partnerId]);
|
|
114
|
+
const handleResumeVerification = useCallback(() => {
|
|
115
|
+
if (!kycStatus?.inquiry)
|
|
116
|
+
return;
|
|
117
|
+
setPersonaError(null);
|
|
118
|
+
setPersonaCancelled(false);
|
|
119
|
+
openPersonaFlow({
|
|
120
|
+
inquiryId: kycStatus.inquiry.inquiryId,
|
|
121
|
+
sessionToken: kycStatus.inquiry.sessionToken,
|
|
122
|
+
environment: kycStatus.config?.environment,
|
|
123
|
+
});
|
|
124
|
+
}, [kycStatus, openPersonaFlow]);
|
|
125
|
+
// No wallet connected — prompt to connect wallet (same pattern as crypto tab)
|
|
126
|
+
if (!address) {
|
|
127
|
+
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: "Connect wallet to pay with card" }), _jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "A wallet connection is required to complete identity verification." })] }), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleConnectWallet, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx(Wallet, { className: "h-4 w-4" }), "Connect Wallet"] }) })] }));
|
|
128
|
+
}
|
|
129
|
+
// Wallet connected but user hasn't kicked off the KYC check yet
|
|
130
|
+
if (!userInitiated) {
|
|
131
|
+
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." })] }), _jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: async () => {
|
|
132
|
+
// Pre-sign in user-gesture context so React Query's queryFn
|
|
133
|
+
// can reuse the cached headers without a second popup.
|
|
134
|
+
// Only enable the query on success — if the user rejects the
|
|
135
|
+
// signature, leave userInitiated=false and stay on this screen.
|
|
136
|
+
try {
|
|
137
|
+
await preCacheKycHeaders();
|
|
138
|
+
setUserInitiated(true);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// User rejected signature — stay on consent screen
|
|
142
|
+
}
|
|
143
|
+
}, children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx(ShieldCheck, { className: "h-4 w-4" }), "Continue to Verify"] }) })] }));
|
|
144
|
+
}
|
|
145
|
+
// Loading KYC status state
|
|
146
|
+
if (isLoadingKycStatus) {
|
|
147
|
+
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..." })] }));
|
|
148
|
+
}
|
|
149
|
+
// Not required or already approved — render nothing
|
|
150
|
+
if (!kycStatus?.kycRequired || kycStatus.status === "approved") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// Persona flow is open - show loading
|
|
154
|
+
if (personaOpen) {
|
|
155
|
+
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" })] }));
|
|
156
|
+
}
|
|
157
|
+
// Needs review or completed (submitted, awaiting Persona decision)
|
|
158
|
+
if (kycStatus.status === "needs_review" || kycStatus.status === "completed") {
|
|
159
|
+
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" })] }));
|
|
160
|
+
}
|
|
161
|
+
// Pending (started before) - offer resume
|
|
162
|
+
if (kycStatus.status === "pending" && kycStatus.inquiry) {
|
|
163
|
+
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"] }) })] }));
|
|
164
|
+
}
|
|
165
|
+
// Not verified / declined / expired - show verification prompt
|
|
166
|
+
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"] }) })] }));
|
|
167
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface VariablePricingConfig {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
minAmount?: string;
|
|
4
|
+
maxAmount?: string;
|
|
5
|
+
suggestedAmount?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
currency?: string;
|
|
8
|
+
}
|
|
9
|
+
interface VariablePricingInputProps {
|
|
10
|
+
config: VariablePricingConfig;
|
|
11
|
+
tokenDecimals: number;
|
|
12
|
+
tokenSymbol: string;
|
|
13
|
+
themeColor?: string;
|
|
14
|
+
onChange: (amountWei: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function VariablePricingInput({ config, tokenDecimals, tokenSymbol, themeColor, onChange, }: VariablePricingInputProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
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 { formatUnits } from "../../../../shared/utils/number.js";
|
|
5
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
7
|
+
export function VariablePricingInput({ config, tokenDecimals, tokenSymbol, themeColor, onChange, }) {
|
|
8
|
+
const currency = config.currency || tokenSymbol;
|
|
9
|
+
// Convert suggested amount from wei to display
|
|
10
|
+
const initialValue = useMemo(() => {
|
|
11
|
+
if (config.suggestedAmount) {
|
|
12
|
+
try {
|
|
13
|
+
return formatUnits(config.suggestedAmount, tokenDecimals);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return "";
|
|
20
|
+
}, [config.suggestedAmount, tokenDecimals]);
|
|
21
|
+
const [displayValue, setDisplayValue] = useState(initialValue);
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
// Min/max in display units
|
|
24
|
+
const minDisplay = useMemo(() => {
|
|
25
|
+
if (!config.minAmount)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
return parseFloat(formatUnits(config.minAmount, tokenDecimals));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}, [config.minAmount, tokenDecimals]);
|
|
34
|
+
const maxDisplay = useMemo(() => {
|
|
35
|
+
if (!config.maxAmount)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
return parseFloat(formatUnits(config.maxAmount, tokenDecimals));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}, [config.maxAmount, tokenDecimals]);
|
|
44
|
+
// Preset amounts
|
|
45
|
+
const presetAmounts = useMemo(() => {
|
|
46
|
+
const presets = [];
|
|
47
|
+
if (config.suggestedAmount) {
|
|
48
|
+
try {
|
|
49
|
+
const suggested = parseFloat(formatUnits(config.suggestedAmount, tokenDecimals));
|
|
50
|
+
const candidates = [suggested / 2, suggested, suggested * 2];
|
|
51
|
+
for (const val of candidates) {
|
|
52
|
+
if (val <= 0)
|
|
53
|
+
continue;
|
|
54
|
+
if (minDisplay !== null && val < minDisplay)
|
|
55
|
+
continue;
|
|
56
|
+
if (maxDisplay !== null && val > maxDisplay)
|
|
57
|
+
continue;
|
|
58
|
+
const display = val % 1 === 0 ? val.toString() : val.toFixed(2);
|
|
59
|
+
presets.push({ label: `${display}`, value: display });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// skip presets
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return presets;
|
|
67
|
+
}, [config.suggestedAmount, tokenDecimals, minDisplay, maxDisplay]);
|
|
68
|
+
const validate = useCallback((value) => {
|
|
69
|
+
const parsed = parseFloat(value);
|
|
70
|
+
if (!value || isNaN(parsed))
|
|
71
|
+
return "Please enter a valid number";
|
|
72
|
+
if (parsed <= 0)
|
|
73
|
+
return "Please enter an amount";
|
|
74
|
+
if (minDisplay !== null && parsed < minDisplay) {
|
|
75
|
+
const display = minDisplay % 1 === 0 ? minDisplay.toString() : minDisplay.toFixed(2);
|
|
76
|
+
return `Minimum amount is ${display} ${currency}`;
|
|
77
|
+
}
|
|
78
|
+
if (maxDisplay !== null && parsed > maxDisplay) {
|
|
79
|
+
const display = maxDisplay % 1 === 0 ? maxDisplay.toString() : maxDisplay.toFixed(2);
|
|
80
|
+
return `Maximum amount is ${display} ${currency}`;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}, [minDisplay, maxDisplay, currency]);
|
|
84
|
+
const convertToWei = useCallback((value) => {
|
|
85
|
+
try {
|
|
86
|
+
const [whole, frac = ""] = value.split(".");
|
|
87
|
+
const paddedFrac = frac.padEnd(tokenDecimals, "0").slice(0, tokenDecimals);
|
|
88
|
+
const factor = BigInt(10) ** BigInt(tokenDecimals);
|
|
89
|
+
return (BigInt(whole || "0") * factor + BigInt(paddedFrac)).toString();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return "0";
|
|
93
|
+
}
|
|
94
|
+
}, [tokenDecimals]);
|
|
95
|
+
const handleChange = useCallback((value) => {
|
|
96
|
+
setDisplayValue(value);
|
|
97
|
+
const validationError = validate(value);
|
|
98
|
+
setError(validationError);
|
|
99
|
+
if (!validationError && value && parseFloat(value) > 0) {
|
|
100
|
+
onChange(convertToWei(value));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
onChange("0");
|
|
104
|
+
}
|
|
105
|
+
}, [validate, convertToWei, onChange]);
|
|
106
|
+
const handlePresetClick = useCallback((value) => {
|
|
107
|
+
setDisplayValue(value);
|
|
108
|
+
setError(null);
|
|
109
|
+
onChange(convertToWei(value));
|
|
110
|
+
}, [convertToWei, onChange]);
|
|
111
|
+
// Notify parent with initial value on mount
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (initialValue && !validate(initialValue)) {
|
|
114
|
+
onChange(convertToWei(initialValue));
|
|
115
|
+
}
|
|
116
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
117
|
+
}, []);
|
|
118
|
+
const formatHint = () => {
|
|
119
|
+
if (minDisplay !== null && maxDisplay !== null) {
|
|
120
|
+
const minStr = minDisplay % 1 === 0 ? minDisplay.toString() : minDisplay.toFixed(2);
|
|
121
|
+
const maxStr = maxDisplay % 1 === 0 ? maxDisplay.toString() : maxDisplay.toFixed(2);
|
|
122
|
+
return `${minStr} – ${maxStr} ${currency}`;
|
|
123
|
+
}
|
|
124
|
+
if (minDisplay !== null) {
|
|
125
|
+
const minStr = minDisplay % 1 === 0 ? minDisplay.toString() : minDisplay.toFixed(2);
|
|
126
|
+
return `Min: ${minStr} ${currency}`;
|
|
127
|
+
}
|
|
128
|
+
if (maxDisplay !== null) {
|
|
129
|
+
const maxStr = maxDisplay % 1 === 0 ? maxDisplay.toString() : maxDisplay.toFixed(2);
|
|
130
|
+
return `Max: ${maxStr} ${currency}`;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
const hint = formatHint();
|
|
135
|
+
return (_jsxs("div", { className: "anyspend-variable-pricing mb-6", children: [_jsx("label", { htmlFor: "variable-pricing-amount", className: "mb-3 block text-lg font-semibold text-gray-900 dark:text-gray-100", children: config.label || "Enter amount" }), presetAmounts.length > 0 && (_jsx("div", { className: "mb-3 flex flex-wrap gap-2", children: presetAmounts.map(preset => (_jsxs("button", { type: "button", onClick: () => handlePresetClick(preset.value), className: cn("rounded-full border px-4 py-2 text-sm font-medium transition-colors", displayValue === preset.value
|
|
136
|
+
? "border-transparent text-white"
|
|
137
|
+
: "border-gray-200 text-gray-700 hover:bg-gray-50 dark:border-neutral-600 dark:text-gray-300 dark:hover:bg-neutral-800"), style: displayValue === preset.value ? { backgroundColor: themeColor || "hsl(var(--as-brand))" } : undefined, children: [preset.label, " ", currency] }, preset.value))) })), _jsxs("div", { className: "relative", children: [_jsx("input", { id: "variable-pricing-amount", type: "text", inputMode: "decimal", value: displayValue, onChange: e => {
|
|
138
|
+
// Normalize comma decimal separators for locale compatibility
|
|
139
|
+
const normalized = e.target.value.replace(",", ".");
|
|
140
|
+
handleChange(normalized);
|
|
141
|
+
}, className: "w-full rounded-xl border border-gray-200 bg-gray-50 px-4 py-3.5 pr-16 text-lg font-semibold text-gray-900 placeholder:text-gray-300 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-600 dark:bg-neutral-800 dark:text-gray-100 dark:placeholder:text-neutral-600 dark:focus:border-blue-400", placeholder: "0.00" }), _jsx("span", { className: "absolute right-4 top-1/2 -translate-y-1/2 text-sm font-medium text-gray-400 dark:text-gray-500", children: currency })] }), hint && _jsx("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: hint }), _jsx(AnimatePresence, { initial: false, children: error && (_jsx(motion.p, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.15, ease: "easeOut" }, className: "mt-1.5 text-sm text-red-500", children: error }, "variable-price-error")) })] }));
|
|
142
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AnySpendCheckout } from "./checkout/AnySpendCheckout";
|
|
2
|
-
export type { AnySpendCheckoutProps, CheckoutItem, CheckoutSummaryLine, AnySpendCheckoutClasses, CheckoutFormSchema, CheckoutFormComponentProps, ShippingOption, DiscountResult, AddressData, } from "./checkout/AnySpendCheckout";
|
|
2
|
+
export type { AnySpendCheckoutProps, CheckoutItem, CheckoutSummaryLine, AnySpendCheckoutClasses, CheckoutFormSchema, CheckoutFormComponentProps, ShippingOption, DiscountResult, AddressData, VariablePricingConfig, } from "./checkout/AnySpendCheckout";
|
|
3
3
|
export { AnySpendCheckoutTrigger } from "./checkout/AnySpendCheckoutTrigger";
|
|
4
4
|
export type { AnySpendCheckoutTriggerProps } from "./checkout/AnySpendCheckoutTrigger";
|
|
5
5
|
export type { PaymentMethod } from "./checkout/CheckoutPaymentPanel";
|
|
@@ -7,6 +7,8 @@ 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 { useAccount } from "wagmi";
|
|
11
|
+
import { getCachedWalletHeaders } from "./useKycStatus.js";
|
|
10
12
|
import { useValidatedClientReferenceId } from "./useValidatedClientReferenceId.js";
|
|
11
13
|
/**
|
|
12
14
|
* Hook for creating onramp orders in the Anyspend protocol
|
|
@@ -15,6 +17,7 @@ import { useValidatedClientReferenceId } from "./useValidatedClientReferenceId.j
|
|
|
15
17
|
export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
16
18
|
// Get B3 context values
|
|
17
19
|
const { partnerId } = useB3Config();
|
|
20
|
+
const { address } = useAccount();
|
|
18
21
|
// Get validated client reference ID from B3 context
|
|
19
22
|
const createValidatedClientReferenceId = useValidatedClientReferenceId();
|
|
20
23
|
// Get fingerprint data
|
|
@@ -36,6 +39,15 @@ export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
|
36
39
|
const srcChain = base.id;
|
|
37
40
|
// Create order with USDC on Base as source
|
|
38
41
|
const srcAmountOnRampInWei = parseUnits(srcFiatAmount, USDC_BASE.decimals);
|
|
42
|
+
// For card payments, include wallet auth headers so the backend can verify
|
|
43
|
+
// KYC by the signing wallet address (may differ from the B3 JWT address).
|
|
44
|
+
// Only use already-cached headers — never trigger a fresh wallet signature
|
|
45
|
+
// here, as that would prompt the user without their explicit consent.
|
|
46
|
+
// KycGate pre-caches the headers in the "Continue to Verify" user-gesture.
|
|
47
|
+
let kycWalletHeaders;
|
|
48
|
+
if (onramp.vendor === "stripe-web2" && address) {
|
|
49
|
+
kycWalletHeaders = getCachedWalletHeaders(address);
|
|
50
|
+
}
|
|
39
51
|
return await anyspendService.createOrder({
|
|
40
52
|
recipientAddress: normalizeAddress(recipientAddress),
|
|
41
53
|
type: orderType,
|
|
@@ -73,6 +85,7 @@ export function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
|
73
85
|
visitorData,
|
|
74
86
|
callbackMetadata: params.callbackMetadata,
|
|
75
87
|
feeOnTop: params.feeOnTop,
|
|
88
|
+
kycWalletHeaders,
|
|
76
89
|
});
|
|
77
90
|
}
|
|
78
91
|
catch (error) {
|
|
@@ -0,0 +1,47 @@
|
|
|
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 cached wallet auth headers without triggering a wallet signature prompt.
|
|
22
|
+
* Returns undefined if no valid cache exists for the given address.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCachedWalletHeaders(address: string): Record<string, string> | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a function that builds the wallet-signature auth headers.
|
|
27
|
+
* Caches signatures for 4 minutes (server allows 5-minute window).
|
|
28
|
+
*/
|
|
29
|
+
export declare function useWalletAuthHeaders(): {
|
|
30
|
+
address: `0x${string}` | undefined;
|
|
31
|
+
getHeaders: () => Promise<Record<string, string>>;
|
|
32
|
+
};
|
|
33
|
+
export declare function useKycStatus(enabled?: boolean): {
|
|
34
|
+
kycStatus: KycStatusResponse | null;
|
|
35
|
+
isLoadingKycStatus: boolean;
|
|
36
|
+
kycStatusError: Error | null;
|
|
37
|
+
refetchKycStatus: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<KycStatusResponse, Error>>;
|
|
38
|
+
};
|
|
39
|
+
export declare function useCreateKycInquiry(): {
|
|
40
|
+
createInquiry: import("@tanstack/react-query").UseMutateAsyncFunction<KycInquiryResponse, Error, void, unknown>;
|
|
41
|
+
isCreatingInquiry: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare function useVerifyKyc(): {
|
|
44
|
+
verifyKyc: import("@tanstack/react-query").UseMutateAsyncFunction<KycVerifyResponse, Error, string, unknown>;
|
|
45
|
+
isVerifying: boolean;
|
|
46
|
+
};
|
|
47
|
+
export {};
|