@b3dotfun/sdk 0.1.66-alpha.0 → 0.1.66-alpha.2
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/react/components/checkout/AnySpendCheckout.d.ts +50 -0
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +30 -0
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +45 -0
- package/dist/cjs/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
- package/dist/cjs/anyspend/react/components/checkout/CartItemRow.js +9 -0
- package/dist/cjs/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
- package/dist/cjs/anyspend/react/components/checkout/CartSummary.js +9 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.js +19 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutLayout.js +25 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.js +45 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
- package/dist/cjs/anyspend/react/components/checkout/CheckoutSuccess.js +11 -0
- package/dist/cjs/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
- package/dist/cjs/anyspend/react/components/checkout/CoinbaseCheckoutPanel.js +27 -0
- package/dist/cjs/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
- package/dist/cjs/anyspend/react/components/checkout/CryptoCheckoutPanel.js +317 -0
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.js +233 -0
- package/dist/cjs/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
- package/dist/cjs/anyspend/react/components/checkout/PoweredByBranding.js +9 -0
- package/dist/cjs/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
- package/dist/cjs/anyspend/react/components/checkout/QRCheckoutPanel.js +148 -0
- package/dist/cjs/anyspend/react/components/index.d.ts +5 -1
- package/dist/cjs/anyspend/react/components/index.js +6 -1
- package/dist/cjs/anyspend/react/components/types/classes.d.ts +32 -0
- package/dist/cjs/app.shared.js +8 -0
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +5 -1
- package/dist/cjs/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/cjs/global-account/react/components/ui/command.d.ts +7 -7
- package/dist/cjs/global-account/react/hooks/useAuth.d.ts +1 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +1 -1
- package/dist/cjs/global-account/react/hooks/useFirstEOA.d.ts +4 -4
- package/dist/cjs/global-account/react/hooks/useUser.d.ts +1 -1
- package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +53 -1
- package/dist/cjs/shared/constants/chains/b3Chain.d.ts +2 -2
- package/dist/cjs/shared/constants/chains/supported.d.ts +3 -3
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.d.ts +50 -0
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +27 -0
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +42 -0
- package/dist/esm/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
- package/dist/esm/anyspend/react/components/checkout/CartItemRow.js +6 -0
- package/dist/esm/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
- package/dist/esm/anyspend/react/components/checkout/CartSummary.js +6 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.js +16 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutLayout.js +22 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.js +42 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
- package/dist/esm/anyspend/react/components/checkout/CheckoutSuccess.js +8 -0
- package/dist/esm/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
- package/dist/esm/anyspend/react/components/checkout/CoinbaseCheckoutPanel.js +24 -0
- package/dist/esm/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
- package/dist/esm/anyspend/react/components/checkout/CryptoCheckoutPanel.js +313 -0
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.js +230 -0
- package/dist/esm/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
- package/dist/esm/anyspend/react/components/checkout/PoweredByBranding.js +6 -0
- package/dist/esm/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
- package/dist/esm/anyspend/react/components/checkout/QRCheckoutPanel.js +145 -0
- package/dist/esm/anyspend/react/components/index.d.ts +5 -1
- package/dist/esm/anyspend/react/components/index.js +3 -0
- package/dist/esm/anyspend/react/components/types/classes.d.ts +32 -0
- package/dist/esm/app.shared.js +8 -0
- package/dist/esm/global-account/react/components/B3DynamicModal.js +5 -1
- package/dist/esm/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/esm/global-account/react/components/ui/command.d.ts +7 -7
- package/dist/esm/global-account/react/hooks/useAuth.d.ts +1 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +1 -1
- package/dist/esm/global-account/react/hooks/useFirstEOA.d.ts +4 -4
- package/dist/esm/global-account/react/hooks/useUser.d.ts +1 -1
- package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +53 -1
- package/dist/esm/shared/constants/chains/b3Chain.d.ts +2 -2
- package/dist/esm/shared/constants/chains/supported.d.ts +3 -3
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/checkout/AnySpendCheckout.d.ts +50 -0
- package/dist/types/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
- package/dist/types/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
- package/dist/types/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
- package/dist/types/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
- package/dist/types/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
- package/dist/types/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
- package/dist/types/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
- package/dist/types/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
- package/dist/types/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
- package/dist/types/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
- package/dist/types/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
- package/dist/types/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
- package/dist/types/anyspend/react/components/index.d.ts +5 -1
- package/dist/types/anyspend/react/components/types/classes.d.ts +32 -0
- package/dist/types/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/types/global-account/react/components/ui/command.d.ts +7 -7
- package/dist/types/global-account/react/hooks/useAuth.d.ts +1 -1
- package/dist/types/global-account/react/hooks/useAuthentication.d.ts +1 -1
- package/dist/types/global-account/react/hooks/useFirstEOA.d.ts +4 -4
- package/dist/types/global-account/react/hooks/useUser.d.ts +1 -1
- package/dist/types/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +53 -1
- package/dist/types/shared/constants/chains/b3Chain.d.ts +2 -2
- package/dist/types/shared/constants/chains/supported.d.ts +3 -3
- package/package.json +1 -1
- package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +127 -0
- package/src/anyspend/react/components/checkout/AnySpendCheckoutTrigger.tsx +166 -0
- package/src/anyspend/react/components/checkout/CartItemRow.tsx +43 -0
- package/src/anyspend/react/components/checkout/CartSummary.tsx +23 -0
- package/src/anyspend/react/components/checkout/CheckoutCartPanel.tsx +60 -0
- package/src/anyspend/react/components/checkout/CheckoutLayout.tsx +72 -0
- package/src/anyspend/react/components/checkout/CheckoutPaymentPanel.tsx +320 -0
- package/src/anyspend/react/components/checkout/CheckoutSuccess.tsx +91 -0
- package/src/anyspend/react/components/checkout/CoinbaseCheckoutPanel.tsx +90 -0
- package/src/anyspend/react/components/checkout/CryptoCheckoutPanel.tsx +643 -0
- package/src/anyspend/react/components/checkout/FiatCheckoutPanel.tsx +568 -0
- package/src/anyspend/react/components/checkout/PoweredByBranding.tsx +32 -0
- package/src/anyspend/react/components/checkout/QRCheckoutPanel.tsx +320 -0
- package/src/anyspend/react/components/index.ts +7 -0
- package/src/anyspend/react/components/types/classes.ts +48 -0
- package/src/app.shared.ts +11 -0
- package/src/global-account/react/components/B3DynamicModal.tsx +5 -0
- package/src/global-account/react/stores/useModalStore.ts +52 -1
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAnyspendCreateOnrampOrder, useGeoOnrampOptions, useStripeClientSecret } from "@b3dotfun/sdk/anyspend/react";
|
|
4
|
+
import { USDC_BASE } from "@b3dotfun/sdk/anyspend/constants";
|
|
5
|
+
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
6
|
+
import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
|
|
7
|
+
import { getStripePromise } from "@b3dotfun/sdk/shared/utils/payment.utils";
|
|
8
|
+
import { TextShimmer, useB3Config, useTokenData } from "@b3dotfun/sdk/global-account/react";
|
|
9
|
+
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
10
|
+
import type { PaymentIntentResult, StripePaymentElementOptions } from "@stripe/stripe-js";
|
|
11
|
+
import { Loader2, Lock } from "lucide-react";
|
|
12
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
13
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
15
|
+
|
|
16
|
+
interface FiatCheckoutPanelProps {
|
|
17
|
+
recipientAddress: string;
|
|
18
|
+
destinationTokenAddress: string;
|
|
19
|
+
destinationTokenChainId: number;
|
|
20
|
+
totalAmount: string;
|
|
21
|
+
themeColor?: string;
|
|
22
|
+
onSuccess?: (result: { txHash?: string; orderId?: string }) => void;
|
|
23
|
+
onError?: (error: Error) => void;
|
|
24
|
+
classes?: AnySpendCheckoutClasses;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function FiatCheckoutPanel({
|
|
28
|
+
recipientAddress,
|
|
29
|
+
destinationTokenAddress,
|
|
30
|
+
destinationTokenChainId,
|
|
31
|
+
totalAmount,
|
|
32
|
+
themeColor,
|
|
33
|
+
onSuccess,
|
|
34
|
+
onError,
|
|
35
|
+
classes,
|
|
36
|
+
}: FiatCheckoutPanelProps) {
|
|
37
|
+
const { data: tokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
|
|
38
|
+
const { theme, stripePublishableKey } = useB3Config();
|
|
39
|
+
|
|
40
|
+
const formattedAmount = useMemo(() => {
|
|
41
|
+
const decimals = tokenData?.decimals || 18;
|
|
42
|
+
return formatTokenAmount(BigInt(totalAmount), decimals);
|
|
43
|
+
}, [totalAmount, tokenData]);
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
geoData,
|
|
47
|
+
stripeOnrampSupport,
|
|
48
|
+
stripeWeb2Support,
|
|
49
|
+
isLoading: isLoadingGeo,
|
|
50
|
+
} = useGeoOnrampOptions(formattedAmount);
|
|
51
|
+
|
|
52
|
+
// Detect if destination token is the same as the onramp source (USDC on Base)
|
|
53
|
+
// In this case, onramp order creation would fail with "Cannot swap same token on same chain"
|
|
54
|
+
const isSameAsOnrampSource = useMemo(() => {
|
|
55
|
+
return (
|
|
56
|
+
destinationTokenChainId === USDC_BASE.chainId &&
|
|
57
|
+
destinationTokenAddress.toLowerCase() === USDC_BASE.address.toLowerCase()
|
|
58
|
+
);
|
|
59
|
+
}, [destinationTokenAddress, destinationTokenChainId]);
|
|
60
|
+
|
|
61
|
+
// Order state
|
|
62
|
+
const [orderId, setOrderId] = useState<string | null>(null);
|
|
63
|
+
const [stripePaymentIntentId, setStripePaymentIntentId] = useState<string | null>(null);
|
|
64
|
+
const [orderError, setOrderError] = useState<string | null>(null);
|
|
65
|
+
const orderCreatedRef = useRef(false);
|
|
66
|
+
|
|
67
|
+
const { createOrder, isCreatingOrder } = useAnyspendCreateOnrampOrder({
|
|
68
|
+
onSuccess: (data: any) => {
|
|
69
|
+
const id = data?.data?.id;
|
|
70
|
+
const intentId = data?.data?.stripePaymentIntentId;
|
|
71
|
+
if (id && intentId) {
|
|
72
|
+
setOrderId(id);
|
|
73
|
+
setStripePaymentIntentId(intentId);
|
|
74
|
+
} else {
|
|
75
|
+
setOrderError("Failed to initialize payment. Please try again.");
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
onError: (error: Error) => {
|
|
79
|
+
setOrderError(error.message || "Failed to create payment order.");
|
|
80
|
+
onError?.(error);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Auto-create onramp order when Stripe Web2 is supported and all data is ready
|
|
85
|
+
// Skip auto-creation when destination is same as source (USDC on Base)
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (
|
|
88
|
+
!isLoadingGeo &&
|
|
89
|
+
stripeWeb2Support?.isSupport &&
|
|
90
|
+
!isSameAsOnrampSource &&
|
|
91
|
+
!orderCreatedRef.current &&
|
|
92
|
+
!orderId &&
|
|
93
|
+
!isCreatingOrder &&
|
|
94
|
+
!orderError &&
|
|
95
|
+
tokenData &&
|
|
96
|
+
recipientAddress
|
|
97
|
+
) {
|
|
98
|
+
orderCreatedRef.current = true;
|
|
99
|
+
|
|
100
|
+
const dstToken = {
|
|
101
|
+
address: destinationTokenAddress,
|
|
102
|
+
chainId: destinationTokenChainId,
|
|
103
|
+
decimals: tokenData.decimals || 18,
|
|
104
|
+
symbol: tokenData.symbol || "",
|
|
105
|
+
name: tokenData.name || "",
|
|
106
|
+
metadata: {
|
|
107
|
+
logoURI: tokenData.logoURI || "",
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
createOrder({
|
|
112
|
+
recipientAddress,
|
|
113
|
+
orderType: "swap",
|
|
114
|
+
dstChain: destinationTokenChainId,
|
|
115
|
+
dstToken,
|
|
116
|
+
srcFiatAmount: formattedAmount,
|
|
117
|
+
onramp: {
|
|
118
|
+
vendor: "stripe-web2",
|
|
119
|
+
paymentMethod: "",
|
|
120
|
+
country: geoData?.country || "US",
|
|
121
|
+
redirectUrl: window.location.origin,
|
|
122
|
+
},
|
|
123
|
+
expectedDstAmount: totalAmount,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}, [
|
|
127
|
+
isLoadingGeo,
|
|
128
|
+
stripeWeb2Support,
|
|
129
|
+
isSameAsOnrampSource,
|
|
130
|
+
orderId,
|
|
131
|
+
isCreatingOrder,
|
|
132
|
+
orderError,
|
|
133
|
+
tokenData,
|
|
134
|
+
recipientAddress,
|
|
135
|
+
destinationTokenAddress,
|
|
136
|
+
destinationTokenChainId,
|
|
137
|
+
formattedAmount,
|
|
138
|
+
totalAmount,
|
|
139
|
+
geoData,
|
|
140
|
+
createOrder,
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// Loading geo/stripe support check
|
|
144
|
+
if (isLoadingGeo) {
|
|
145
|
+
return (
|
|
146
|
+
<motion.div
|
|
147
|
+
initial={{ opacity: 0 }}
|
|
148
|
+
animate={{ opacity: 1 }}
|
|
149
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
150
|
+
className={cn("anyspend-fiat-loading flex flex-col items-center gap-3 py-6", classes?.fiatPanel)}
|
|
151
|
+
>
|
|
152
|
+
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
|
|
153
|
+
<TextShimmer duration={1.5} className="text-sm">
|
|
154
|
+
Loading payment form...
|
|
155
|
+
</TextShimmer>
|
|
156
|
+
</motion.div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const hasStripeWeb2 = stripeWeb2Support && stripeWeb2Support.isSupport;
|
|
161
|
+
const hasStripeRedirect = !!stripeOnrampSupport;
|
|
162
|
+
|
|
163
|
+
// Not available in region
|
|
164
|
+
if (!hasStripeWeb2 && !hasStripeRedirect) {
|
|
165
|
+
return (
|
|
166
|
+
<motion.div
|
|
167
|
+
initial={{ opacity: 0, y: 8 }}
|
|
168
|
+
animate={{ opacity: 1, y: 0 }}
|
|
169
|
+
transition={{ duration: 0.25, ease: "easeOut" }}
|
|
170
|
+
className={cn("anyspend-fiat-unavailable py-4 text-center", classes?.fiatPanel)}
|
|
171
|
+
>
|
|
172
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
173
|
+
Card payments are not available in your region for this amount.
|
|
174
|
+
</p>
|
|
175
|
+
</motion.div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Order creation error - show with retry
|
|
180
|
+
if (orderError) {
|
|
181
|
+
return (
|
|
182
|
+
<motion.div
|
|
183
|
+
initial={{ opacity: 0, y: 8 }}
|
|
184
|
+
animate={{ opacity: 1, y: 0 }}
|
|
185
|
+
transition={{ duration: 0.25, ease: "easeOut" }}
|
|
186
|
+
className={cn("anyspend-fiat-error flex flex-col items-center gap-3 py-4", classes?.fiatPanel)}
|
|
187
|
+
>
|
|
188
|
+
<p className="text-sm text-red-500">{orderError}</p>
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => {
|
|
191
|
+
setOrderError(null);
|
|
192
|
+
orderCreatedRef.current = false;
|
|
193
|
+
}}
|
|
194
|
+
className="anyspend-fiat-retry text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
|
195
|
+
>
|
|
196
|
+
Try again
|
|
197
|
+
</button>
|
|
198
|
+
</motion.div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Same-token fallback: USDC on Base uses Stripe redirect instead of embedded
|
|
203
|
+
if (isSameAsOnrampSource && hasStripeRedirect) {
|
|
204
|
+
return (
|
|
205
|
+
<div className={cn("anyspend-fiat-redirect flex flex-col gap-3 py-2", classes?.fiatPanel)}>
|
|
206
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
207
|
+
You'll be redirected to Stripe to complete your payment securely.
|
|
208
|
+
</p>
|
|
209
|
+
<button
|
|
210
|
+
className={cn(
|
|
211
|
+
"anyspend-fiat-redirect-btn flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-semibold text-white transition-all active:scale-[0.98]",
|
|
212
|
+
"bg-blue-600 hover:bg-blue-700",
|
|
213
|
+
)}
|
|
214
|
+
style={themeColor ? { backgroundColor: themeColor } : undefined}
|
|
215
|
+
>
|
|
216
|
+
<Lock className="h-3.5 w-3.5" />
|
|
217
|
+
Pay with Card
|
|
218
|
+
</button>
|
|
219
|
+
<p className="anyspend-fiat-secured flex items-center justify-center gap-1 text-xs text-gray-400">
|
|
220
|
+
<Lock className="h-3 w-3" />
|
|
221
|
+
Secured by Stripe
|
|
222
|
+
</p>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Same-token without redirect support
|
|
228
|
+
if (isSameAsOnrampSource) {
|
|
229
|
+
return (
|
|
230
|
+
<div className={cn("anyspend-fiat-unavailable py-4 text-center", classes?.fiatPanel)}>
|
|
231
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
232
|
+
Card payments are not available for this token configuration.
|
|
233
|
+
</p>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Creating order / waiting for PaymentIntent
|
|
239
|
+
if (hasStripeWeb2 && (isCreatingOrder || !stripePaymentIntentId)) {
|
|
240
|
+
return (
|
|
241
|
+
<motion.div
|
|
242
|
+
initial={{ opacity: 0 }}
|
|
243
|
+
animate={{ opacity: 1 }}
|
|
244
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
245
|
+
className={cn("anyspend-fiat-initializing flex flex-col items-center gap-3 py-6", classes?.fiatPanel)}
|
|
246
|
+
>
|
|
247
|
+
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
|
|
248
|
+
<TextShimmer duration={1.5} className="text-sm">
|
|
249
|
+
Initializing secure payment...
|
|
250
|
+
</TextShimmer>
|
|
251
|
+
</motion.div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Stripe Web2 embedded form
|
|
256
|
+
if (hasStripeWeb2 && stripePaymentIntentId && orderId) {
|
|
257
|
+
return (
|
|
258
|
+
<motion.div
|
|
259
|
+
initial={{ opacity: 0, y: 10 }}
|
|
260
|
+
animate={{ opacity: 1, y: 0 }}
|
|
261
|
+
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
262
|
+
className={cn("anyspend-fiat-stripe", classes?.fiatPanel)}
|
|
263
|
+
>
|
|
264
|
+
<StripeCheckout
|
|
265
|
+
stripePaymentIntentId={stripePaymentIntentId}
|
|
266
|
+
stripePublishableKey={stripePublishableKey}
|
|
267
|
+
theme={theme}
|
|
268
|
+
themeColor={themeColor}
|
|
269
|
+
orderId={orderId}
|
|
270
|
+
onSuccess={onSuccess}
|
|
271
|
+
onError={onError}
|
|
272
|
+
classes={classes}
|
|
273
|
+
/>
|
|
274
|
+
</motion.div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fallback: Stripe redirect flow (only if web2 not available but redirect is)
|
|
279
|
+
return (
|
|
280
|
+
<div className={cn("anyspend-fiat-redirect flex flex-col gap-3 py-2", classes?.fiatPanel)}>
|
|
281
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
282
|
+
You'll be redirected to Stripe to complete your payment securely.
|
|
283
|
+
</p>
|
|
284
|
+
<button
|
|
285
|
+
className={cn(
|
|
286
|
+
"anyspend-fiat-redirect-btn flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-semibold text-white transition-all active:scale-[0.98]",
|
|
287
|
+
"bg-blue-600 hover:bg-blue-700",
|
|
288
|
+
)}
|
|
289
|
+
style={themeColor ? { backgroundColor: themeColor } : undefined}
|
|
290
|
+
>
|
|
291
|
+
<Lock className="h-3.5 w-3.5" />
|
|
292
|
+
Pay with Card
|
|
293
|
+
</button>
|
|
294
|
+
<p className="anyspend-fiat-secured flex items-center justify-center gap-1 text-xs text-gray-400">
|
|
295
|
+
<Lock className="h-3 w-3" />
|
|
296
|
+
Secured by Stripe
|
|
297
|
+
</p>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// -------------------------------------------------------------------
|
|
303
|
+
// Stripe Elements wrapper - fetches client secret, renders Elements
|
|
304
|
+
// -------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
interface StripeCheckoutProps {
|
|
307
|
+
stripePaymentIntentId: string;
|
|
308
|
+
stripePublishableKey?: string | null;
|
|
309
|
+
theme?: string;
|
|
310
|
+
themeColor?: string;
|
|
311
|
+
orderId: string;
|
|
312
|
+
onSuccess?: (result: { txHash?: string; orderId?: string }) => void;
|
|
313
|
+
onError?: (error: Error) => void;
|
|
314
|
+
classes?: AnySpendCheckoutClasses;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function StripeCheckout({
|
|
318
|
+
stripePaymentIntentId,
|
|
319
|
+
stripePublishableKey,
|
|
320
|
+
theme,
|
|
321
|
+
themeColor,
|
|
322
|
+
orderId,
|
|
323
|
+
onSuccess,
|
|
324
|
+
onError,
|
|
325
|
+
classes,
|
|
326
|
+
}: StripeCheckoutProps) {
|
|
327
|
+
const { clientSecret, isLoadingStripeClientSecret, stripeClientSecretError } =
|
|
328
|
+
useStripeClientSecret(stripePaymentIntentId);
|
|
329
|
+
|
|
330
|
+
if (isLoadingStripeClientSecret) {
|
|
331
|
+
return (
|
|
332
|
+
<motion.div
|
|
333
|
+
initial={{ opacity: 0 }}
|
|
334
|
+
animate={{ opacity: 1 }}
|
|
335
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
336
|
+
className="anyspend-stripe-loading flex flex-col items-center gap-3 py-6"
|
|
337
|
+
>
|
|
338
|
+
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
|
|
339
|
+
<TextShimmer duration={1.5} className="text-sm">
|
|
340
|
+
Loading payment form...
|
|
341
|
+
</TextShimmer>
|
|
342
|
+
</motion.div>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (stripeClientSecretError || !clientSecret) {
|
|
347
|
+
return (
|
|
348
|
+
<motion.div
|
|
349
|
+
initial={{ opacity: 0, y: 8 }}
|
|
350
|
+
animate={{ opacity: 1, y: 0 }}
|
|
351
|
+
transition={{ duration: 0.25, ease: "easeOut" }}
|
|
352
|
+
className="anyspend-stripe-error py-4 text-center"
|
|
353
|
+
>
|
|
354
|
+
<p className="text-sm text-red-500">
|
|
355
|
+
{stripeClientSecretError?.message || "Failed to load payment form. Please try again."}
|
|
356
|
+
</p>
|
|
357
|
+
</motion.div>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
<Elements
|
|
363
|
+
stripe={getStripePromise(stripePublishableKey)}
|
|
364
|
+
options={{
|
|
365
|
+
clientSecret,
|
|
366
|
+
appearance: {
|
|
367
|
+
theme: theme === "light" ? "stripe" : "night",
|
|
368
|
+
variables: {
|
|
369
|
+
borderRadius: "8px",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
<StripeCheckoutForm
|
|
375
|
+
themeColor={themeColor}
|
|
376
|
+
orderId={orderId}
|
|
377
|
+
onSuccess={onSuccess}
|
|
378
|
+
onError={onError}
|
|
379
|
+
classes={classes}
|
|
380
|
+
/>
|
|
381
|
+
</Elements>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// -------------------------------------------------------------------
|
|
386
|
+
// Inner form component (inside Elements context)
|
|
387
|
+
// -------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
interface StripeCheckoutFormProps {
|
|
390
|
+
themeColor?: string;
|
|
391
|
+
orderId: string;
|
|
392
|
+
onSuccess?: (result: { txHash?: string; orderId?: string }) => void;
|
|
393
|
+
onError?: (error: Error) => void;
|
|
394
|
+
classes?: AnySpendCheckoutClasses;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function StripeCheckoutForm({ themeColor, orderId, onSuccess, onError, classes }: StripeCheckoutFormProps) {
|
|
398
|
+
const stripe = useStripe();
|
|
399
|
+
const elements = useElements();
|
|
400
|
+
|
|
401
|
+
const [loading, setLoading] = useState(false);
|
|
402
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
403
|
+
const [stripeReady, setStripeReady] = useState(false);
|
|
404
|
+
const [showAddressElement, setShowAddressElement] = useState(false);
|
|
405
|
+
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (stripe && elements) {
|
|
408
|
+
setStripeReady(true);
|
|
409
|
+
}
|
|
410
|
+
}, [stripe, elements]);
|
|
411
|
+
|
|
412
|
+
const handlePaymentElementChange = useCallback((event: any) => {
|
|
413
|
+
setShowAddressElement(event.value?.type === "card");
|
|
414
|
+
}, []);
|
|
415
|
+
|
|
416
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
|
|
419
|
+
if (!stripe || !elements) {
|
|
420
|
+
setMessage("Payment system is not ready. Please wait.");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
setLoading(true);
|
|
425
|
+
setMessage(null);
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const result = (await stripe.confirmPayment({
|
|
429
|
+
elements,
|
|
430
|
+
redirect: "if_required",
|
|
431
|
+
})) as PaymentIntentResult;
|
|
432
|
+
|
|
433
|
+
if (result.error) {
|
|
434
|
+
setMessage(result.error.message || "Payment failed. Please try again.");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Payment succeeded
|
|
439
|
+
onSuccess?.({ orderId, txHash: undefined });
|
|
440
|
+
} catch (error: any) {
|
|
441
|
+
const errorMessage = error?.message || "Payment failed. Please try again.";
|
|
442
|
+
setMessage(errorMessage);
|
|
443
|
+
onError?.(error instanceof Error ? error : new Error(errorMessage));
|
|
444
|
+
} finally {
|
|
445
|
+
setLoading(false);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const stripeElementOptions: StripePaymentElementOptions = {
|
|
450
|
+
layout: "tabs" as const,
|
|
451
|
+
fields: {
|
|
452
|
+
billingDetails: "auto" as const,
|
|
453
|
+
},
|
|
454
|
+
wallets: {
|
|
455
|
+
applePay: "auto" as const,
|
|
456
|
+
googlePay: "auto" as const,
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
if (!stripeReady) {
|
|
461
|
+
return (
|
|
462
|
+
<motion.div
|
|
463
|
+
initial={{ opacity: 0 }}
|
|
464
|
+
animate={{ opacity: 1 }}
|
|
465
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
466
|
+
className="anyspend-stripe-loading flex flex-col items-center gap-3 py-6"
|
|
467
|
+
>
|
|
468
|
+
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
|
|
469
|
+
<TextShimmer duration={1.5} className="text-sm">
|
|
470
|
+
Loading payment form...
|
|
471
|
+
</TextShimmer>
|
|
472
|
+
</motion.div>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return (
|
|
477
|
+
<motion.form
|
|
478
|
+
initial={{ opacity: 0, y: 10 }}
|
|
479
|
+
animate={{ opacity: 1, y: 0 }}
|
|
480
|
+
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
481
|
+
onSubmit={handleSubmit}
|
|
482
|
+
className="anyspend-stripe-form flex flex-col gap-4"
|
|
483
|
+
>
|
|
484
|
+
<div className="anyspend-stripe-payment-element">
|
|
485
|
+
<PaymentElement onChange={handlePaymentElementChange} options={stripeElementOptions} />
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<AnimatePresence initial={false}>
|
|
489
|
+
{showAddressElement && (
|
|
490
|
+
<motion.div
|
|
491
|
+
key="address-element"
|
|
492
|
+
initial={{ height: 0, opacity: 0 }}
|
|
493
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
494
|
+
exit={{ height: 0, opacity: 0 }}
|
|
495
|
+
transition={{ duration: 0.25, ease: "easeOut" }}
|
|
496
|
+
style={{ overflow: "hidden" }}
|
|
497
|
+
className="anyspend-stripe-address-element"
|
|
498
|
+
>
|
|
499
|
+
<AddressElement
|
|
500
|
+
options={{
|
|
501
|
+
mode: "billing",
|
|
502
|
+
fields: {
|
|
503
|
+
phone: "always",
|
|
504
|
+
},
|
|
505
|
+
display: {
|
|
506
|
+
name: "split",
|
|
507
|
+
},
|
|
508
|
+
validation: {
|
|
509
|
+
phone: {
|
|
510
|
+
required: "always",
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
}}
|
|
514
|
+
/>
|
|
515
|
+
</motion.div>
|
|
516
|
+
)}
|
|
517
|
+
</AnimatePresence>
|
|
518
|
+
|
|
519
|
+
{/* Error message */}
|
|
520
|
+
<AnimatePresence initial={false}>
|
|
521
|
+
{message && (
|
|
522
|
+
<motion.div
|
|
523
|
+
key="stripe-error"
|
|
524
|
+
initial={{ opacity: 0, height: 0 }}
|
|
525
|
+
animate={{ opacity: 1, height: "auto" }}
|
|
526
|
+
exit={{ opacity: 0, height: 0 }}
|
|
527
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
528
|
+
style={{ overflow: "hidden" }}
|
|
529
|
+
className="anyspend-stripe-error rounded-lg border border-red-200 bg-red-50 px-3 py-2 dark:border-red-800 dark:bg-red-900/20"
|
|
530
|
+
>
|
|
531
|
+
<p className="text-sm text-red-600 dark:text-red-400">{message}</p>
|
|
532
|
+
</motion.div>
|
|
533
|
+
)}
|
|
534
|
+
</AnimatePresence>
|
|
535
|
+
|
|
536
|
+
{/* Submit button */}
|
|
537
|
+
<button
|
|
538
|
+
type="submit"
|
|
539
|
+
disabled={!stripe || !elements || loading}
|
|
540
|
+
className={cn(
|
|
541
|
+
"anyspend-stripe-submit flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-semibold text-white transition-all",
|
|
542
|
+
!stripe || !elements || loading
|
|
543
|
+
? "cursor-not-allowed bg-gray-300 dark:bg-gray-600"
|
|
544
|
+
: "bg-blue-600 hover:bg-blue-700 active:scale-[0.98]",
|
|
545
|
+
classes?.payButton,
|
|
546
|
+
)}
|
|
547
|
+
style={themeColor && !loading ? { backgroundColor: themeColor } : undefined}
|
|
548
|
+
>
|
|
549
|
+
{loading ? (
|
|
550
|
+
<>
|
|
551
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
552
|
+
Processing...
|
|
553
|
+
</>
|
|
554
|
+
) : (
|
|
555
|
+
<>
|
|
556
|
+
<Lock className="h-3.5 w-3.5" />
|
|
557
|
+
Complete Payment
|
|
558
|
+
</>
|
|
559
|
+
)}
|
|
560
|
+
</button>
|
|
561
|
+
|
|
562
|
+
<p className="anyspend-fiat-secured flex items-center justify-center gap-1 text-xs text-gray-400">
|
|
563
|
+
<Lock className="h-3 w-3" />
|
|
564
|
+
Secured by Stripe
|
|
565
|
+
</p>
|
|
566
|
+
</motion.form>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
4
|
+
import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
5
|
+
|
|
6
|
+
interface PoweredByBrandingProps {
|
|
7
|
+
organizationName?: string;
|
|
8
|
+
organizationLogo?: string;
|
|
9
|
+
classes?: AnySpendCheckoutClasses;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function PoweredByBranding({ organizationName, organizationLogo, classes }: PoweredByBrandingProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={cn("flex items-center justify-between pt-4", classes?.poweredBy)}>
|
|
15
|
+
{organizationLogo || organizationName ? (
|
|
16
|
+
<div className="flex items-center gap-2">
|
|
17
|
+
{organizationLogo && (
|
|
18
|
+
<img src={organizationLogo} alt={organizationName || "Organization"} className="h-5 w-5 rounded-full" />
|
|
19
|
+
)}
|
|
20
|
+
{organizationName && (
|
|
21
|
+
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">{organizationName}</span>
|
|
22
|
+
)}
|
|
23
|
+
</div>
|
|
24
|
+
) : (
|
|
25
|
+
<div />
|
|
26
|
+
)}
|
|
27
|
+
<span className="text-xs text-gray-400 dark:text-gray-500">
|
|
28
|
+
powered by <span className="font-medium text-gray-500 dark:text-gray-400">anyspend</span>
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|