@b3dotfun/sdk 0.0.79-alpha.1 → 0.0.79-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.
Files changed (50) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.js +8 -1
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  3. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +53 -13
  4. package/dist/cjs/anyspend/react/components/AnySpendNFT.js +6 -9
  5. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +1 -3
  6. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.d.ts +2 -1
  7. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.js +32 -12
  8. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +6 -6
  9. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +5 -3
  10. package/dist/cjs/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  11. package/dist/cjs/anyspend/react/hooks/useGeoOnrampOptions.js +4 -2
  12. package/dist/cjs/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  13. package/dist/cjs/anyspend/react/hooks/useStripeSupport.js +1 -0
  14. package/dist/cjs/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +5 -3
  15. package/dist/cjs/shared/utils/ipfs.d.ts +3 -3
  16. package/dist/cjs/shared/utils/ipfs.js +8 -8
  17. package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.js +8 -1
  18. package/dist/esm/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  19. package/dist/esm/anyspend/react/components/AnySpendCustom.js +53 -13
  20. package/dist/esm/anyspend/react/components/AnySpendNFT.js +7 -10
  21. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +1 -3
  22. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.d.ts +2 -1
  23. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.js +32 -12
  24. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +6 -6
  25. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +5 -3
  26. package/dist/esm/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  27. package/dist/esm/anyspend/react/hooks/useGeoOnrampOptions.js +4 -2
  28. package/dist/esm/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  29. package/dist/esm/anyspend/react/hooks/useStripeSupport.js +1 -0
  30. package/dist/esm/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +5 -3
  31. package/dist/esm/shared/utils/ipfs.d.ts +3 -3
  32. package/dist/esm/shared/utils/ipfs.js +8 -8
  33. package/dist/styles/index.css +1 -1
  34. package/dist/types/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  35. package/dist/types/anyspend/react/components/common/FiatPaymentMethod.d.ts +2 -1
  36. package/dist/types/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  37. package/dist/types/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  38. package/dist/types/shared/utils/ipfs.d.ts +3 -3
  39. package/package.json +1 -1
  40. package/src/anyspend/react/components/AnySpendCollectorClubPurchase.tsx +8 -0
  41. package/src/anyspend/react/components/AnySpendCustom.tsx +71 -26
  42. package/src/anyspend/react/components/AnySpendNFT.tsx +49 -48
  43. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +1 -4
  44. package/src/anyspend/react/components/common/FiatPaymentMethod.tsx +45 -21
  45. package/src/anyspend/react/components/common/PanelOnramp.tsx +13 -13
  46. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +53 -16
  47. package/src/anyspend/react/hooks/useGeoOnrampOptions.ts +5 -2
  48. package/src/anyspend/react/hooks/useStripeSupport.ts +1 -0
  49. package/src/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.tsx +5 -7
  50. package/src/shared/utils/ipfs.ts +8 -8
@@ -44,7 +44,6 @@ import { motion } from "motion/react";
44
44
  import React, { useCallback, useEffect, useMemo, useState } from "react";
45
45
 
46
46
  import { base } from "viem/chains";
47
- import { useFeatureFlags } from "../contexts/FeatureFlagsContext";
48
47
  import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
49
48
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
50
49
  import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
@@ -188,6 +187,8 @@ export function AnySpendCustom(props: {
188
187
  onSuccess?: (txHash?: string) => void;
189
188
  showRecipient?: boolean;
190
189
  onShowPointsDetail?: () => void;
190
+ /** Fiat amount in USD for fiat payments */
191
+ srcFiatAmount?: string;
191
192
  }) {
192
193
  const fingerprintConfig = getFingerprintConfig();
193
194
 
@@ -215,6 +216,7 @@ function AnySpendCustomInner({
215
216
  onSuccess,
216
217
  showRecipient = true,
217
218
  onShowPointsDetail,
219
+ srcFiatAmount: srcFiatAmountProps,
218
220
  }: {
219
221
  loadOrder?: string;
220
222
  mode?: "modal" | "page";
@@ -238,9 +240,9 @@ function AnySpendCustomInner({
238
240
  onSuccess?: (txHash?: string) => void;
239
241
  showRecipient?: boolean;
240
242
  onShowPointsDetail?: () => void;
243
+ srcFiatAmount?: string;
241
244
  }) {
242
245
  const hasMounted = useHasMounted();
243
- const featureFlags = useFeatureFlags();
244
246
 
245
247
  const searchParams = useSearchParamsSSR();
246
248
  const router = useRouter();
@@ -402,14 +404,31 @@ function AnySpendCustomInner({
402
404
  }
403
405
  }, [activeTab, anyspendQuote?.data]);
404
406
  const formattedSrcAmount = srcAmount ? formatTokenAmount(srcAmount, srcToken.decimals, 6, false) : null;
405
- const srcFiatAmount = useMemo(
406
- () => (activeTab === "fiat" && srcAmount ? formatUnits(srcAmount.toString(), USDC_BASE.decimals) : "0"),
407
- [activeTab, srcAmount],
408
- );
407
+ // Calculate fiat amount for geo check (regardless of activeTab) to determine tab availability
408
+ const srcFiatAmountForGeoCheck = useMemo(() => {
409
+ // Use prop if provided
410
+ if (srcFiatAmountProps) {
411
+ return srcFiatAmountProps;
412
+ }
413
+ // Fallback to dstAmount if destination token is USDC
414
+ if (dstAmount && dstToken.address.toLowerCase() === USDC_BASE.address.toLowerCase()) {
415
+ return formatUnits(dstAmount, USDC_BASE.decimals);
416
+ }
417
+ // Use srcAmount if available (from quote)
418
+ if (srcAmount) {
419
+ return formatUnits(srcAmount.toString(), USDC_BASE.decimals);
420
+ }
421
+ return "0";
422
+ }, [srcAmount, srcFiatAmountProps, dstAmount, dstToken.address]);
423
+
424
+ const srcFiatAmount = useMemo(() => {
425
+ if (activeTab !== "fiat") return "0";
426
+ return srcFiatAmountForGeoCheck;
427
+ }, [activeTab, srcFiatAmountForGeoCheck]);
409
428
 
410
- // Get geo data and onramp options (after quote is available)
411
- const { geoData, isOnrampSupported, coinbaseAvailablePaymentMethods, stripeWeb2Support } =
412
- useGeoOnrampOptions(srcFiatAmount);
429
+ // Get geo data and onramp options (use srcFiatAmountForGeoCheck to check availability regardless of activeTab)
430
+ const { geoData, isOnrampSupported, coinbaseAvailablePaymentMethods, stripeOnrampSupport, stripeWeb2Support } =
431
+ useGeoOnrampOptions(srcFiatAmountForGeoCheck);
413
432
 
414
433
  useEffect(() => {
415
434
  if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
@@ -521,9 +540,14 @@ function AnySpendCustomInner({
521
540
  // Get the current geo data from the hook
522
541
  const currentGeoData = geoData;
523
542
 
543
+ // Use total amount from quote (includes fees) for onramp, fallback to srcFiatAmount
544
+ const onrampAmount = anyspendQuote?.data?.currencyIn?.amountUsd
545
+ ? anyspendQuote.data.currencyIn.amountUsd.toString()
546
+ : srcFiatAmount;
547
+
524
548
  void createOnrampOrder({
525
549
  ...createOrderParams,
526
- srcFiatAmount: srcFiatAmount,
550
+ srcFiatAmount: onrampAmount,
527
551
  onramp: {
528
552
  vendor: onramp.vendor,
529
553
  paymentMethod: onramp.paymentMethod,
@@ -601,12 +625,21 @@ function AnySpendCustomInner({
601
625
  }
602
626
  vendor = "coinbase";
603
627
  paymentMethodString = coinbaseAvailablePaymentMethods[0]?.id || "";
604
- } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
628
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE_WEB2) {
629
+ // Stripe Web2 embedded payment
605
630
  if (!stripeWeb2Support || !stripeWeb2Support.isSupport) {
606
- toast.error("Stripe not available");
631
+ toast.error("Stripe embedded payment not available");
607
632
  return;
608
633
  }
609
- vendor = stripeWeb2Support && stripeWeb2Support.isSupport ? "stripe-web2" : "stripe";
634
+ vendor = "stripe-web2";
635
+ paymentMethodString = "";
636
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
637
+ // Stripe redirect (one-click buy URL)
638
+ if (!stripeOnrampSupport) {
639
+ toast.error("Stripe redirect payment not available");
640
+ return;
641
+ }
642
+ vendor = "stripe";
610
643
  paymentMethodString = "";
611
644
  } else {
612
645
  toast.error("Please select a payment method");
@@ -803,7 +836,7 @@ function AnySpendCustomInner({
803
836
 
804
837
  // Render points badge if conditions are met
805
838
  const renderPointsBadge = () => {
806
- if (featureFlags.showPoints && anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0) {
839
+ if (anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0) {
807
840
  return (
808
841
  <PointsBadge
809
842
  pointsAmount={anyspendQuote.data.pointsAmount}
@@ -1162,20 +1195,32 @@ function AnySpendCustomInner({
1162
1195
  <div className="flex flex-col items-end gap-0.5">
1163
1196
  <div className="flex flex-wrap items-center justify-end gap-2">
1164
1197
  {renderPointsBadge()}
1165
- <span className="text-as-primary whitespace-nowrap text-xl font-semibold">
1166
- ${srcFiatAmount || "0.00"}
1167
- </span>
1198
+ {isLoadingAnyspendQuote ? (
1199
+ <div className="bg-as-surface-secondary h-7 w-16 animate-pulse rounded" />
1200
+ ) : (
1201
+ <span className="text-as-primary whitespace-nowrap text-xl font-semibold">
1202
+ {anyspendQuote?.data?.currencyIn?.amountUsd ? (
1203
+ `$${Number(anyspendQuote.data.currencyIn.amountUsd).toFixed(2)}`
1204
+ ) : (
1205
+ <>
1206
+ ${parseFloat(srcFiatAmount || "0").toFixed(2)}
1207
+ <span className="text-as-tertiarry text-base">+</span>
1208
+ </>
1209
+ )}
1210
+ </span>
1211
+ )}
1168
1212
  </div>
1169
- {anyspendQuote?.data?.fee?.type === "stripeweb2_fee" && anyspendQuote.data.fee.originalAmount && (
1170
- <span className="text-as-secondary text-xs">
1171
- incl. $
1172
- {(
1213
+ {(() => {
1214
+ if (anyspendQuote?.data?.fee?.type === "stripeweb2_fee" && anyspendQuote.data.fee.originalAmount) {
1215
+ const fee =
1173
1216
  (Number(anyspendQuote.data.fee.originalAmount) - Number(anyspendQuote.data.fee.finalAmount)) /
1174
- 1e6
1175
- ).toFixed(2)}{" "}
1176
- fee
1177
- </span>
1178
- )}
1217
+ 1e6;
1218
+ if (fee > 0) {
1219
+ return <span className="text-as-secondary text-xs">incl. ${fee.toFixed(2)} fee</span>;
1220
+ }
1221
+ }
1222
+ return null;
1223
+ })()}
1179
1224
  </div>
1180
1225
  </motion.div>
1181
1226
  </div>
@@ -7,7 +7,7 @@ import { getIpfsUrl } from "@b3dotfun/sdk/shared/utils/ipfs";
7
7
  import { formatDisplayNumber, formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
8
8
  import { MoreVertical } from "lucide-react";
9
9
  import { AnimatePresence } from "motion/react";
10
- import { useEffect, useState } from "react";
10
+ import { useCallback, useEffect, useRef, useState } from "react";
11
11
  import { b3 } from "viem/chains";
12
12
  import { GetQuoteResponse } from "../../types/api_req_res";
13
13
  import { AnySpendCustom } from "./AnySpendCustom";
@@ -46,19 +46,19 @@ export function AnySpendNFT({
46
46
  onShowPointsDetail?: () => void;
47
47
  }) {
48
48
  const [imageUrlWithFallback, setFallbackImageUrl] = useState<string | null>(nftContract.imageUrl);
49
- const [isLoadingFallback, setIsLoadingFallback] = useState(false);
49
+ const hasFetchedRef = useRef(false);
50
50
 
51
51
  // Fetch contract metadata when imageUrl is empty
52
52
  useEffect(() => {
53
53
  async function fetchContractMetadata() {
54
54
  // fetch image Uri if not provided
55
- if (nftContract.imageUrl || isLoadingFallback) {
55
+ if (nftContract.imageUrl || hasFetchedRef.current) {
56
56
  return;
57
57
  }
58
58
 
59
- try {
60
- setIsLoadingFallback(true);
59
+ hasFetchedRef.current = true;
61
60
 
61
+ try {
62
62
  // Use the chainIdToPublicClient utility function
63
63
  const publicClient = chainIdToPublicClient(nftContract.chainId);
64
64
 
@@ -96,56 +96,57 @@ export function AnySpendNFT({
96
96
  }
97
97
  } catch (error) {
98
98
  console.error("Error fetching contract metadata:", error);
99
- } finally {
100
- setIsLoadingFallback(false);
101
99
  }
102
100
  }
103
101
 
104
102
  fetchContractMetadata();
105
- }, [nftContract.contractAddress, nftContract.chainId, nftContract.imageUrl, nftContract.tokenId, isLoadingFallback]);
106
-
107
- const header = ({
108
- anyspendPrice,
109
- isLoadingAnyspendPrice,
110
- }: {
111
- anyspendPrice: GetQuoteResponse | undefined;
112
- isLoadingAnyspendPrice: boolean;
113
- }) => (
114
- <>
115
- <div className="relative size-[200px]">
116
- <div className="absolute inset-0 scale-95 bg-black/30 blur-md"></div>
117
- <GlareCard className="overflow-hidden">
118
- {imageUrlWithFallback && (
119
- <img src={imageUrlWithFallback} alt={nftContract.name} className="size-full object-cover" />
120
- )}
121
- <div className="absolute inset-0 rounded-xl border border-white/10"></div>
122
- </GlareCard>
123
-
124
- <DropdownMenu nftContract={nftContract} />
125
- </div>
126
- <div className="from-b3-react-background to-as-on-surface-1 -mb-5 mt-[-100px] w-full rounded-t-lg bg-gradient-to-t">
127
- <div className="h-[100px] w-full" />
128
- <div className="mb-1 flex w-full flex-col items-center gap-2 p-5">
129
- <span className="font-sf-rounded text-2xl font-semibold">{nftContract.name}</span>
130
-
131
- <div className="flex w-fit items-center gap-1">
132
- {anyspendPrice ? (
133
- <AnimatePresence mode="wait">
134
- <div
135
- className={cn("text-as-primary group flex items-center text-3xl font-semibold transition-all", {
136
- "opacity-0": isLoadingAnyspendPrice,
137
- })}
138
- >
139
- {formatDisplayNumber(anyspendPrice?.data?.currencyIn?.amountUsd, { style: "currency" })}
140
- </div>
141
- </AnimatePresence>
142
- ) : (
143
- <div className="h-[36px] w-full" />
103
+ }, [nftContract.contractAddress, nftContract.chainId, nftContract.imageUrl, nftContract.tokenId]);
104
+
105
+ const header = useCallback(
106
+ ({
107
+ anyspendPrice,
108
+ isLoadingAnyspendPrice,
109
+ }: {
110
+ anyspendPrice: GetQuoteResponse | undefined;
111
+ isLoadingAnyspendPrice: boolean;
112
+ }) => (
113
+ <>
114
+ <div className="relative size-[200px]">
115
+ <div className="absolute inset-0 scale-95 bg-black/30 blur-md"></div>
116
+ <GlareCard className="overflow-hidden">
117
+ {imageUrlWithFallback && (
118
+ <img src={imageUrlWithFallback} alt={nftContract.name} className="size-full object-cover" />
144
119
  )}
120
+ <div className="absolute inset-0 rounded-xl border border-white/10"></div>
121
+ </GlareCard>
122
+
123
+ <DropdownMenu nftContract={nftContract} />
124
+ </div>
125
+ <div className="from-b3-react-background to-as-on-surface-1 -mb-5 mt-[-100px] w-full rounded-t-lg bg-gradient-to-t">
126
+ <div className="h-[100px] w-full" />
127
+ <div className="mb-1 flex w-full flex-col items-center gap-2 p-5">
128
+ <span className="font-sf-rounded text-2xl font-semibold">{nftContract.name}</span>
129
+
130
+ <div className="flex w-fit items-center gap-1">
131
+ {anyspendPrice ? (
132
+ <AnimatePresence mode="wait">
133
+ <div
134
+ className={cn("text-as-primary group flex items-center text-3xl font-semibold transition-all", {
135
+ "opacity-0": isLoadingAnyspendPrice,
136
+ })}
137
+ >
138
+ {formatDisplayNumber(anyspendPrice?.data?.currencyIn?.amountUsd, { style: "currency" })}
139
+ </div>
140
+ </AnimatePresence>
141
+ ) : (
142
+ <div className="h-[36px] w-full" />
143
+ )}
144
+ </div>
145
145
  </div>
146
146
  </div>
147
- </div>
148
- </>
147
+ </>
148
+ ),
149
+ [imageUrlWithFallback, nftContract],
149
150
  );
150
151
 
151
152
  return (
@@ -5,7 +5,6 @@ import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
5
  import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
7
  import { components } from "../../../types/api";
8
- import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
9
8
  import { OrderTokenAmount } from "./OrderTokenAmount";
10
9
  import { PointsBadge } from "./PointsBadge";
11
10
 
@@ -55,8 +54,6 @@ export function CryptoReceiveSection({
55
54
  onShowPointsDetail,
56
55
  onShowFeeDetail,
57
56
  }: CryptoReceiveSectionProps) {
58
- const featureFlags = useFeatureFlags();
59
-
60
57
  return (
61
58
  <motion.div
62
59
  initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
@@ -184,7 +181,7 @@ export function CryptoReceiveSection({
184
181
  );
185
182
  })()}
186
183
  </div>
187
- {featureFlags.showPoints && anyspendQuote?.data?.pointsAmount > 0 && (
184
+ {anyspendQuote?.data?.pointsAmount > 0 && (
188
185
  <PointsBadge
189
186
  pointsAmount={anyspendQuote.data.pointsAmount}
190
187
  pointsMultiplier={anyspendQuote.data.pointsMultiplier}
@@ -7,7 +7,8 @@ import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
7
7
  export enum FiatPaymentMethod {
8
8
  NONE = "none",
9
9
  COINBASE_PAY = "coinbase_pay",
10
- STRIPE = "stripe",
10
+ STRIPE = "stripe", // Stripe redirect (one-click buy URL)
11
+ STRIPE_WEB2 = "stripe_web2", // Stripe embedded payment
11
12
  }
12
13
 
13
14
  interface FiatPaymentMethodProps {
@@ -25,30 +26,34 @@ export function FiatPaymentMethodComponent({
25
26
  onSelectPaymentMethod,
26
27
  srcAmountOnRamp,
27
28
  }: FiatPaymentMethodProps) {
29
+ // Load geo-based onramp options like in PanelOnramp
30
+ const {
31
+ coinbaseAvailablePaymentMethods,
32
+ stripeOnrampSupport,
33
+ stripeWeb2Support,
34
+ isLoading: isLoadingGeoOnramp,
35
+ } = useGeoOnrampOptions(srcAmountOnRamp);
36
+
28
37
  // Helper function to get fees from API data
29
38
  const getFeeFromApi = (paymentMethod: FiatPaymentMethod): string | null => {
30
39
  switch (paymentMethod) {
31
40
  case FiatPaymentMethod.COINBASE_PAY:
32
41
  // Coinbase doesn't provide fee info in API, return null
33
42
  return null;
34
- case FiatPaymentMethod.STRIPE:
35
- // Get fee from Stripe API response
43
+ case FiatPaymentMethod.STRIPE_WEB2:
44
+ // Get fee from Stripe Web2 API response
36
45
  if (stripeWeb2Support && "formattedFeeUsd" in stripeWeb2Support) {
37
46
  return stripeWeb2Support.formattedFeeUsd;
38
47
  }
39
48
  return null;
49
+ case FiatPaymentMethod.STRIPE:
50
+ // Stripe redirect doesn't have fee info from API
51
+ return null;
40
52
  default:
41
53
  return null; // No fee when no payment method selected
42
54
  }
43
55
  };
44
56
 
45
- // Load geo-based onramp options like in PanelOnramp
46
- const {
47
- coinbaseAvailablePaymentMethods,
48
- stripeWeb2Support,
49
- isLoading: isLoadingGeoOnramp,
50
- } = useGeoOnrampOptions(srcAmountOnRamp);
51
-
52
57
  // Generate payment methods based on geo availability (like in PanelOnrampPayment)
53
58
  const availablePaymentMethods = [];
54
59
 
@@ -65,15 +70,28 @@ export function FiatPaymentMethodComponent({
65
70
  });
66
71
  }
67
72
 
68
- // Add Stripe if available
69
- if (stripeWeb2Support && stripeWeb2Support.isSupport) {
70
- const stripeFee = getFeeFromApi(FiatPaymentMethod.STRIPE);
73
+ // Add Stripe redirect (one-click) if available - primary option
74
+ if (stripeOnrampSupport) {
75
+ const stripeFee = getFeeFromApi(FiatPaymentMethod.STRIPE_WEB2); // Use same fee estimate
71
76
  availablePaymentMethods.push({
72
77
  id: FiatPaymentMethod.STRIPE,
73
- name: "Stripe",
74
- description: "Credit or debit card payment",
75
- badge: stripeFee ? `$${Number(stripeFee).toFixed(2)} fee` : "Standard Fee",
76
- badgeColor: "bg-yellow-100 text-yellow-800",
78
+ name: "Credit/Debit Card",
79
+ description: "Pay via Stripe checkout",
80
+ badge: stripeFee ? `$${Number(stripeFee).toFixed(2)} fee` : undefined,
81
+ badgeColor: "bg-gray-100 text-gray-800",
82
+ available: true,
83
+ });
84
+ }
85
+
86
+ // Add Stripe Web2 (embedded) if available - secondary option
87
+ if (stripeWeb2Support && stripeWeb2Support.isSupport) {
88
+ const stripeFee = getFeeFromApi(FiatPaymentMethod.STRIPE_WEB2);
89
+ availablePaymentMethods.push({
90
+ id: FiatPaymentMethod.STRIPE_WEB2,
91
+ name: "Quick Pay",
92
+ description: "Credit or debit card",
93
+ badge: stripeFee ? `$${Number(stripeFee).toFixed(2)} fee` : undefined,
94
+ badgeColor: "bg-gray-100 text-gray-800",
77
95
  available: true,
78
96
  });
79
97
  }
@@ -144,16 +162,22 @@ export function FiatPaymentMethodComponent({
144
162
  >
145
163
  {/* Icon - matching PanelOnramp style */}
146
164
  <div className="flex h-12 w-12 items-center justify-center rounded-full bg-blue-600 text-2xl text-white">
147
- {method.id === FiatPaymentMethod.COINBASE_PAY ? "C" : "S"}
165
+ {method.id === FiatPaymentMethod.COINBASE_PAY
166
+ ? "C"
167
+ : method.id === FiatPaymentMethod.STRIPE || method.id === FiatPaymentMethod.STRIPE_WEB2
168
+ ? "S"
169
+ : "?"}
148
170
  </div>
149
171
 
150
172
  {/* Content */}
151
173
  <div className="flex flex-1 flex-col items-start text-left">
152
174
  <div className="flex items-center gap-2">
153
175
  <span className="text-as-primary text-base font-semibold">{method.name}</span>
154
- <span className={cn("rounded-full px-2 py-1 text-xs font-medium", method.badgeColor)}>
155
- {method.badge}
156
- </span>
176
+ {method.badge && (
177
+ <span className={cn("rounded-full px-2 py-1 text-xs font-medium", method.badgeColor)}>
178
+ {method.badge}
179
+ </span>
180
+ )}
157
181
  </div>
158
182
  <span className="text-as-primary/60 text-sm">{method.description}</span>
159
183
  </div>
@@ -8,7 +8,6 @@ import { formatAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
8
8
  import { ChevronRight, Info, Wallet } from "lucide-react";
9
9
  import { useRef } from "react";
10
10
 
11
- import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
12
11
  import { FiatPaymentMethod } from "./FiatPaymentMethod";
13
12
  import { OrderTokenAmountFiat } from "./OrderTokenAmountFiat";
14
13
  import { PointsBadge } from "./PointsBadge";
@@ -54,8 +53,6 @@ export function PanelOnramp({
54
53
  onShowFeeDetail?: () => void;
55
54
  customUsdInputValues?: string[];
56
55
  }) {
57
- const featureFlags = useFeatureFlags();
58
-
59
56
  // Helper function to get fees from anyspend quote
60
57
  const getFeeFromApi = (paymentMethod: FiatPaymentMethod): number | null => {
61
58
  // Try to get fee from anyspend quote first (most accurate)
@@ -84,11 +81,16 @@ export function PanelOnramp({
84
81
  const getTotalAmount = (paymentMethod: FiatPaymentMethod): number => {
85
82
  const baseAmount = parseFloat(srcAmountOnRamp) || 5;
86
83
 
87
- // Try to get from anyspend quote first (most accurate)
84
+ // For stripeweb2_fee, use the originalAmount
88
85
  if (anyspendQuote?.data?.fee?.type === "stripeweb2_fee") {
89
86
  return Number(anyspendQuote.data.fee.originalAmount) / 1e6; // Convert from wei to USD
90
87
  }
91
88
 
89
+ // Use currencyIn.amountUsd from quote when available (includes fees, most accurate for custom orders)
90
+ if (anyspendQuote?.data?.currencyIn?.amountUsd) {
91
+ return Number(anyspendQuote.data.currencyIn.amountUsd);
92
+ }
93
+
92
94
  const fee = getFeeFromApi(paymentMethod);
93
95
 
94
96
  // For Coinbase or when fee is available, calculate manually
@@ -279,15 +281,13 @@ export function PanelOnramp({
279
281
  <Info className="h-4 w-4" />
280
282
  </button>
281
283
  )}
282
- {featureFlags.showPoints &&
283
- anyspendQuote?.data?.pointsAmount &&
284
- anyspendQuote?.data?.pointsAmount > 0 && (
285
- <PointsBadge
286
- pointsAmount={anyspendQuote.data.pointsAmount}
287
- pointsMultiplier={anyspendQuote.data.pointsMultiplier}
288
- onClick={() => onShowPointsDetail?.()}
289
- />
290
- )}
284
+ {anyspendQuote?.data?.pointsAmount && anyspendQuote?.data?.pointsAmount > 0 && (
285
+ <PointsBadge
286
+ pointsAmount={anyspendQuote.data.pointsAmount}
287
+ pointsMultiplier={anyspendQuote.data.pointsMultiplier}
288
+ onClick={() => onShowPointsDetail?.()}
289
+ />
290
+ )}
291
291
  </div>
292
292
  <div className="flex flex-col items-end gap-0.5">
293
293
  <span className="text-as-primary font-semibold">
@@ -65,6 +65,7 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
65
65
  geoData,
66
66
  coinbaseOnrampOptions,
67
67
  coinbaseAvailablePaymentMethods,
68
+ stripeOnrampSupport,
68
69
  stripeWeb2Support,
69
70
  isLoading: isLoadingGeoOnramp,
70
71
  } = useGeoOnrampOptions(srcAmountOnRamp);
@@ -201,16 +202,18 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
201
202
  >
202
203
  ${parseFloat(srcAmountOnRamp).toFixed(2)}
203
204
  </p>
204
- {anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd && (
205
- <p className="text-b3-react-foreground/60 text-xs">
206
- incl. $
207
- {(
208
- (Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) /
209
- 10000
210
- ).toFixed(2)}{" "}
211
- fee
212
- </p>
213
- )}
205
+ {anyspendQuote?.data?.fee?.type === "standard_fee" &&
206
+ anyspendQuote.data.currencyIn?.amountUsd &&
207
+ anyspendQuote.data.fee.finalFeeBps > 0 && (
208
+ <p className="text-b3-react-foreground/60 text-xs">
209
+ incl. $
210
+ {(
211
+ (Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) /
212
+ 10000
213
+ ).toFixed(2)}{" "}
214
+ fee
215
+ </p>
216
+ )}
214
217
  </div>
215
218
  </div>
216
219
  </div>
@@ -311,7 +314,37 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
311
314
  );
312
315
  })()}
313
316
 
314
- {/* Stripe Option - Show if supported */}
317
+ {/* Stripe Redirect Option - Primary option */}
318
+ {stripeOnrampSupport && (
319
+ <button
320
+ onClick={() => handlePaymentMethodClick("stripe")}
321
+ className="bg-b3-react-background border-b3-react-border hover:border-as-brand group flex w-full items-center justify-between gap-4 rounded-xl border p-5 transition-all duration-200 hover:shadow-md"
322
+ >
323
+ <div className="flex items-center gap-4">
324
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-purple-50">
325
+ <img
326
+ src="https://raw.githubusercontent.com/stripe/stripe.github.io/455f506a628dc3f6c505e3001db45a64e29e9fc3/images/stripe-logo.svg"
327
+ alt="Stripe"
328
+ className="h-5"
329
+ />
330
+ </div>
331
+ <div className="flex flex-col items-start text-left">
332
+ <h4 className="text-b3-react-foreground text-lg font-semibold">Credit/Debit Card</h4>
333
+ <p className="text-b3-react-foreground/60 text-sm">Pay via Stripe checkout</p>
334
+ {stripeWeb2Support?.isSupport && stripeWeb2Support.formattedFeeUsd && (
335
+ <div className="mt-1">
336
+ <span className="text-xs text-gray-500">
337
+ ${Number(stripeWeb2Support.formattedFeeUsd).toFixed(2)} fee
338
+ </span>
339
+ </div>
340
+ )}
341
+ </div>
342
+ </div>
343
+ <ChevronRight className="text-b3-react-foreground/40 group-hover:text-b3-react-foreground/60 h-5 w-5 transition-colors" />
344
+ </button>
345
+ )}
346
+
347
+ {/* Stripe Web2 Option - Embedded payment (secondary) */}
315
348
  {stripeWeb2Support.isSupport && (
316
349
  <button
317
350
  onClick={() => handlePaymentMethodClick("stripe-web2")}
@@ -326,11 +359,15 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
326
359
  />
327
360
  </div>
328
361
  <div className="flex flex-col items-start text-left">
329
- <h4 className="text-b3-react-foreground text-lg font-semibold">Stripe</h4>
330
- <p className="text-b3-react-foreground/60 text-sm">Credit or debit card payment</p>
331
- <div className="mt-1 flex items-center gap-1">
332
- <span className="text-xs font-medium text-orange-600">Fee Applied</span>
333
- </div>
362
+ <h4 className="text-b3-react-foreground text-lg font-semibold">Quick Pay</h4>
363
+ <p className="text-b3-react-foreground/60 text-sm">Credit or debit card</p>
364
+ {stripeWeb2Support.formattedFeeUsd && (
365
+ <div className="mt-1">
366
+ <span className="text-xs text-gray-500">
367
+ ${Number(stripeWeb2Support.formattedFeeUsd).toFixed(2)} fee
368
+ </span>
369
+ </div>
370
+ )}
334
371
  </div>
335
372
  </div>
336
373
  <ChevronRight className="text-b3-react-foreground/40 group-hover:text-b3-react-foreground/60 h-5 w-5 transition-colors" />
@@ -20,7 +20,7 @@ export function useGeoOnrampOptions(srcFiatAmount: string) {
20
20
  const { geoData, loading: isLoadingGeo, error: geoError } = useGetGeo();
21
21
  const { coinbaseOnrampOptions, isLoadingCoinbaseOnrampOptions, coinbaseOnrampOptionsError } =
22
22
  useCoinbaseOnrampOptions(geoData?.country, visitorData);
23
- const { stripeWeb2Support, isLoadingStripeSupport, stripeSupportError } = useStripeSupport(
23
+ const { stripeOnrampSupport, stripeWeb2Support, isLoadingStripeSupport, stripeSupportError } = useStripeSupport(
24
24
  srcFiatAmount,
25
25
  visitorData,
26
26
  );
@@ -43,8 +43,10 @@ export function useGeoOnrampOptions(srcFiatAmount: string) {
43
43
  geoData,
44
44
  coinbaseOnrampOptions,
45
45
  coinbaseAvailablePaymentMethods,
46
+ stripeOnrampSupport,
46
47
  stripeWeb2Support,
47
- isOnrampSupported: coinbaseAvailablePaymentMethods.length > 0 || stripeWeb2Support,
48
+ isOnrampSupported:
49
+ coinbaseAvailablePaymentMethods.length > 0 || stripeOnrampSupport || stripeWeb2Support.isSupport,
48
50
  isLoading: isLoadingGeo || isLoadingCoinbaseOnrampOptions || isLoadingStripeSupport || isLoadingVisitorData,
49
51
  isLoadingGeo,
50
52
  isLoadingCoinbaseOnrampOptions,
@@ -57,6 +59,7 @@ export function useGeoOnrampOptions(srcFiatAmount: string) {
57
59
  geoData,
58
60
  coinbaseOnrampOptions,
59
61
  coinbaseAvailablePaymentMethods,
62
+ stripeOnrampSupport,
60
63
  stripeWeb2Support,
61
64
  isLoadingGeo,
62
65
  isLoadingCoinbaseOnrampOptions,
@@ -12,6 +12,7 @@ export function useStripeSupport(usdAmount?: string, visitorData?: VisitorData,
12
12
 
13
13
  return useMemo(
14
14
  () => ({
15
+ stripeOnrampSupport: data?.stripeOnramp || false,
15
16
  stripeWeb2Support: data?.stripeWeb2 || { isSupport: false },
16
17
  isLoadingStripeSupport: isLoading,
17
18
  stripeSupportError: error,