@b3dotfun/sdk 0.0.26-alpha.1 → 0.0.26-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.
@@ -48,36 +48,22 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
48
48
  const [amount, setAmount] = (0, react_3.useState)(null);
49
49
  const [stripeReady, setStripeReady] = (0, react_3.useState)(false);
50
50
  const [showHowItWorks, setShowHowItWorks] = (0, react_3.useState)(false);
51
- // Require-completion flags for Card & Address
52
- const [cardComplete, setCardComplete] = (0, react_3.useState)(false);
53
- const [addressComplete, setAddressComplete] = (0, react_3.useState)(false);
54
- // Snapshot of AddressElement value to pass to billing_details
55
- const [addressValue, setAddressValue] = (0, react_3.useState)(null);
56
- // Helper function to mark "How it works" as seen
57
- const markHowItWorksAsSeen = (0, react_3.useCallback)(() => {
58
- setShowHowItWorks(false);
59
- localStorage.setItem("b3-payment-how-it-works-seen", "true");
60
- }, []);
51
+ const [showAddressElement, setShowAddressElement] = (0, react_3.useState)(false);
61
52
  (0, react_3.useEffect)(() => {
62
53
  if (stripe && elements) {
63
54
  setStripeReady(true);
64
55
  }
65
56
  }, [stripe, elements, order.id]);
66
- // Check if user has seen "How it works" before
67
- (0, react_3.useEffect)(() => {
68
- const hasSeenHowItWorks = localStorage.getItem("b3-payment-how-it-works-seen");
69
- if (!hasSeenHowItWorks) {
70
- setShowHowItWorks(true);
71
- }
72
- }, []);
73
57
  (0, react_3.useEffect)(() => {
74
58
  const fetchPaymentIntent = async () => {
75
59
  if (!stripe || !clientSecret)
76
60
  return;
77
61
  try {
78
62
  const paymentIntent = await stripe.retrievePaymentIntent(clientSecret);
79
- const amt = paymentIntent.paymentIntent?.amount ? (0, payment_utils_1.formatStripeAmount)(paymentIntent.paymentIntent.amount) : null;
80
- setAmount(amt);
63
+ const amount = paymentIntent.paymentIntent?.amount
64
+ ? (0, payment_utils_1.formatStripeAmount)(paymentIntent.paymentIntent.amount)
65
+ : null;
66
+ setAmount(amount);
81
67
  }
82
68
  catch (error) {
83
69
  console.error("@@stripe-web2-payment:retrieve-intent-error:", JSON.stringify(error, null, 2));
@@ -85,50 +71,23 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
85
71
  };
86
72
  fetchPaymentIntent();
87
73
  }, [clientSecret, stripe]);
74
+ // Handle payment element changes
75
+ const handlePaymentElementChange = (event) => {
76
+ // Show address element only for card payments
77
+ setShowAddressElement(event.value.type === "card");
78
+ };
88
79
  const handleSubmit = async (e) => {
89
80
  e.preventDefault();
90
- if (!stripe || !elements || !clientSecret) {
81
+ if (!stripe || !elements) {
91
82
  setMessage("Stripe is not initialized");
92
83
  return;
93
84
  }
94
85
  setLoading(true);
95
86
  setMessage(null);
96
- // Block submission until both card and billing address are complete
97
- if (!cardComplete || !addressComplete) {
98
- setMessage("Please complete all required billing address and card fields.");
99
- setLoading(false);
100
- return;
101
- }
102
87
  try {
103
- const card = elements.getElement(react_stripe_js_1.CardElement);
104
- if (!card) {
105
- setMessage("Card element not found");
106
- setLoading(false);
107
- return;
108
- }
109
- const result = (await stripe.confirmCardPayment(clientSecret, {
110
- payment_method: {
111
- card,
112
- billing_details: {
113
- // Map AddressElement values into billing_details
114
- name: addressValue?.name
115
- ? typeof addressValue.name === "string"
116
- ? addressValue.name
117
- : [addressValue.name.firstName, addressValue.name.lastName].filter(Boolean).join(" ")
118
- : undefined,
119
- phone: addressValue?.phone || undefined,
120
- address: addressValue?.address
121
- ? {
122
- line1: addressValue.address.line1 || undefined,
123
- line2: addressValue.address.line2 || undefined,
124
- city: addressValue.address.city || undefined,
125
- state: addressValue.address.state || undefined, // province/region
126
- postal_code: addressValue.address.postal_code || undefined,
127
- country: addressValue.address.country || undefined,
128
- }
129
- : undefined,
130
- },
131
- },
88
+ const result = (await stripe.confirmPayment({
89
+ elements,
90
+ redirect: "if_required",
132
91
  }));
133
92
  if (result.error) {
134
93
  // This point will only be reached if there is an immediate error.
@@ -175,6 +134,16 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
175
134
  if (!stripeReady) {
176
135
  return (0, jsx_runtime_1.jsx)(StripeLoadingState, {});
177
136
  }
137
+ const stripeElementOptions = {
138
+ layout: "tabs",
139
+ fields: {
140
+ billingDetails: "auto",
141
+ },
142
+ wallets: {
143
+ applePay: "auto",
144
+ googlePay: "auto",
145
+ },
146
+ };
178
147
  const howItWorksSteps = [
179
148
  {
180
149
  number: 1,
@@ -189,19 +158,20 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
189
158
  description: "After payment confirmation, your order will be processed and completed automatically",
190
159
  },
191
160
  ];
192
- return ((0, jsx_runtime_1.jsxs)("div", { className: "relative mt-1 flex w-full flex-1 flex-col items-center justify-center", children: [(0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "w-full space-y-6", children: [(0, jsx_runtime_1.jsx)(react_1.OrderDetailsCollapsible, { order: order, dstToken: order.metadata.dstToken, tournament: order.type === "join_tournament" || order.type === "fund_tournament" ? order.metadata.tournament : undefined, nft: order.type === "mint_nft" ? order.metadata.nft : undefined, recipientName: recipientName, formattedExpectedDstAmount: (0, number_1.formatTokenAmount)(BigInt(order.srcAmount), order.metadata.dstToken.decimals), showTotal: true, totalAmount: amount ? `$${Number(amount).toFixed(2)}` : undefined }), (0, jsx_runtime_1.jsxs)("div", { className: "w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), (0, jsx_runtime_1.jsx)("div", { className: "border-as-stroke mb-4 rounded-lg border p-3", children: (0, jsx_runtime_1.jsx)(react_stripe_js_1.CardElement, { options: {}, onChange: (e) => setCardComplete(!!e.complete) }) }), (0, jsx_runtime_1.jsx)(react_stripe_js_1.AddressElement, { options: {
161
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "relative mt-1 flex w-full flex-1 flex-col items-center justify-center", children: [(0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "w-full space-y-6", children: [(0, jsx_runtime_1.jsx)(react_1.OrderDetailsCollapsible, { order: order, dstToken: order.metadata.dstToken, tournament: order.type === "join_tournament" || order.type === "fund_tournament" ? order.metadata.tournament : undefined, nft: order.type === "mint_nft" ? order.metadata.nft : undefined, recipientName: recipientName, formattedExpectedDstAmount: (0, number_1.formatTokenAmount)(BigInt(order.srcAmount), order.metadata.dstToken.decimals), showTotal: true, totalAmount: amount ? `$${Number(amount).toFixed(2)}` : undefined }), (0, jsx_runtime_1.jsxs)("div", { className: "w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), (0, jsx_runtime_1.jsx)(react_stripe_js_1.PaymentElement, { onChange: handlePaymentElementChange, options: stripeElementOptions }), showAddressElement && ((0, jsx_runtime_1.jsx)(react_stripe_js_1.AddressElement, { options: {
193
162
  mode: "billing",
194
- fields: { phone: "always" },
195
- display: { name: "split" },
196
- validation: { phone: { required: "always" } },
197
- },
198
- // Ensure we mirror Stripe's built-in completeness signal and capture values
199
- onChange: (e) => {
200
- setAddressComplete(!!e.complete);
201
- setAddressValue(e.value);
202
- } })] }), message && ((0, jsx_runtime_1.jsxs)("div", { className: "bg-as-red/10 border-as-red/20 flex w-full items-center gap-3 rounded-2xl border p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-as-red flex h-6 w-6 shrink-0 items-center justify-center rounded-full", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-as-red text-sm font-medium", children: message })] })), (0, jsx_runtime_1.jsx)(react_2.ShinyButton, { type: "submit", accentColor: "hsl(var(--as-brand))", disabled: !stripe || !elements || loading || !cardComplete || !addressComplete, className: "relative w-full py-4 text-lg font-semibold", children: loading ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-current border-t-transparent" }), (0, jsx_runtime_1.jsx)("span", { className: "text-white", children: "Processing Payment..." })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-white", children: "Complete Payment" }), amount && (0, jsx_runtime_1.jsxs)("span", { className: "text-white/90", children: ["$", Number(amount).toFixed(2)] })] })) })] }), showHowItWorks && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", onClick: e => {
203
- if (e.target === e.currentTarget) {
204
- markHowItWorksAsSeen();
205
- }
206
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-6 flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-as-primary text-xl font-semibold", children: "How it works" }), (0, jsx_runtime_1.jsx)("button", { onClick: markHowItWorksAsSeen, className: "text-as-primary/60 hover:text-as-primary transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-6 w-6" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsx)(PaymentMethodIcons_1.default, {}), (0, jsx_runtime_1.jsx)(HowItWorks_1.default, { steps: howItWorksSteps })] })] }) }))] }));
163
+ fields: {
164
+ phone: "always",
165
+ },
166
+ // More granular control
167
+ display: {
168
+ name: "split", // or 'split' for first/last name separately
169
+ },
170
+ // Validation
171
+ validation: {
172
+ phone: {
173
+ required: "always", // or 'always', 'never'
174
+ },
175
+ },
176
+ } }))] }), message && ((0, jsx_runtime_1.jsxs)("div", { className: "bg-as-red/10 border-as-red/20 flex w-full items-center gap-3 rounded-2xl border p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-as-red flex h-6 w-6 shrink-0 items-center justify-center rounded-full", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-as-red text-sm font-medium", children: message })] })), (0, jsx_runtime_1.jsx)(react_2.ShinyButton, { type: "submit", accentColor: "hsl(var(--as-brand))", disabled: !stripe || !elements || loading, className: "relative w-full py-4 text-lg font-semibold", children: loading ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-current border-t-transparent" }), (0, jsx_runtime_1.jsx)("span", { className: "text-white", children: "Processing Payment..." })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-white", children: "Complete Payment" }), amount && (0, jsx_runtime_1.jsxs)("span", { className: "text-white/90", children: ["$", Number(amount).toFixed(2)] })] })) })] }), showHowItWorks && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-6 flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-as-primary text-xl font-semibold", children: "How it works" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowHowItWorks(false), className: "text-as-primary/60 hover:text-as-primary transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-6 w-6" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsx)(PaymentMethodIcons_1.default, {}), (0, jsx_runtime_1.jsx)(HowItWorks_1.default, { steps: howItWorksSteps })] })] }) }))] }));
207
177
  }
@@ -4,10 +4,10 @@ import { OrderDetailsCollapsible, useStripeClientSecret } from "../../../../anys
4
4
  import { ShinyButton, useB3, useModalStore, useProfile } from "../../../../global-account/react/index.js";
5
5
  import { formatTokenAmount } from "../../../../shared/utils/number.js";
6
6
  import { formatStripeAmount } from "../../../../shared/utils/payment.utils.js";
7
- import { AddressElement, CardElement, Elements, useElements, useStripe } from "@stripe/react-stripe-js";
7
+ import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
8
8
  import { loadStripe } from "@stripe/stripe-js";
9
9
  import { X } from "lucide-react";
10
- import { useCallback, useEffect, useState } from "react";
10
+ import { useEffect, useState } from "react";
11
11
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "../AnySpendFingerprintWrapper.js";
12
12
  import HowItWorks from "./HowItWorks.js";
13
13
  import PaymentMethodIcons from "./PaymentMethodIcons.js";
@@ -42,36 +42,22 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
42
42
  const [amount, setAmount] = useState(null);
43
43
  const [stripeReady, setStripeReady] = useState(false);
44
44
  const [showHowItWorks, setShowHowItWorks] = useState(false);
45
- // Require-completion flags for Card & Address
46
- const [cardComplete, setCardComplete] = useState(false);
47
- const [addressComplete, setAddressComplete] = useState(false);
48
- // Snapshot of AddressElement value to pass to billing_details
49
- const [addressValue, setAddressValue] = useState(null);
50
- // Helper function to mark "How it works" as seen
51
- const markHowItWorksAsSeen = useCallback(() => {
52
- setShowHowItWorks(false);
53
- localStorage.setItem("b3-payment-how-it-works-seen", "true");
54
- }, []);
45
+ const [showAddressElement, setShowAddressElement] = useState(false);
55
46
  useEffect(() => {
56
47
  if (stripe && elements) {
57
48
  setStripeReady(true);
58
49
  }
59
50
  }, [stripe, elements, order.id]);
60
- // Check if user has seen "How it works" before
61
- useEffect(() => {
62
- const hasSeenHowItWorks = localStorage.getItem("b3-payment-how-it-works-seen");
63
- if (!hasSeenHowItWorks) {
64
- setShowHowItWorks(true);
65
- }
66
- }, []);
67
51
  useEffect(() => {
68
52
  const fetchPaymentIntent = async () => {
69
53
  if (!stripe || !clientSecret)
70
54
  return;
71
55
  try {
72
56
  const paymentIntent = await stripe.retrievePaymentIntent(clientSecret);
73
- const amt = paymentIntent.paymentIntent?.amount ? formatStripeAmount(paymentIntent.paymentIntent.amount) : null;
74
- setAmount(amt);
57
+ const amount = paymentIntent.paymentIntent?.amount
58
+ ? formatStripeAmount(paymentIntent.paymentIntent.amount)
59
+ : null;
60
+ setAmount(amount);
75
61
  }
76
62
  catch (error) {
77
63
  console.error("@@stripe-web2-payment:retrieve-intent-error:", JSON.stringify(error, null, 2));
@@ -79,50 +65,23 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
79
65
  };
80
66
  fetchPaymentIntent();
81
67
  }, [clientSecret, stripe]);
68
+ // Handle payment element changes
69
+ const handlePaymentElementChange = (event) => {
70
+ // Show address element only for card payments
71
+ setShowAddressElement(event.value.type === "card");
72
+ };
82
73
  const handleSubmit = async (e) => {
83
74
  e.preventDefault();
84
- if (!stripe || !elements || !clientSecret) {
75
+ if (!stripe || !elements) {
85
76
  setMessage("Stripe is not initialized");
86
77
  return;
87
78
  }
88
79
  setLoading(true);
89
80
  setMessage(null);
90
- // Block submission until both card and billing address are complete
91
- if (!cardComplete || !addressComplete) {
92
- setMessage("Please complete all required billing address and card fields.");
93
- setLoading(false);
94
- return;
95
- }
96
81
  try {
97
- const card = elements.getElement(CardElement);
98
- if (!card) {
99
- setMessage("Card element not found");
100
- setLoading(false);
101
- return;
102
- }
103
- const result = (await stripe.confirmCardPayment(clientSecret, {
104
- payment_method: {
105
- card,
106
- billing_details: {
107
- // Map AddressElement values into billing_details
108
- name: addressValue?.name
109
- ? typeof addressValue.name === "string"
110
- ? addressValue.name
111
- : [addressValue.name.firstName, addressValue.name.lastName].filter(Boolean).join(" ")
112
- : undefined,
113
- phone: addressValue?.phone || undefined,
114
- address: addressValue?.address
115
- ? {
116
- line1: addressValue.address.line1 || undefined,
117
- line2: addressValue.address.line2 || undefined,
118
- city: addressValue.address.city || undefined,
119
- state: addressValue.address.state || undefined, // province/region
120
- postal_code: addressValue.address.postal_code || undefined,
121
- country: addressValue.address.country || undefined,
122
- }
123
- : undefined,
124
- },
125
- },
82
+ const result = (await stripe.confirmPayment({
83
+ elements,
84
+ redirect: "if_required",
126
85
  }));
127
86
  if (result.error) {
128
87
  // This point will only be reached if there is an immediate error.
@@ -169,6 +128,16 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
169
128
  if (!stripeReady) {
170
129
  return _jsx(StripeLoadingState, {});
171
130
  }
131
+ const stripeElementOptions = {
132
+ layout: "tabs",
133
+ fields: {
134
+ billingDetails: "auto",
135
+ },
136
+ wallets: {
137
+ applePay: "auto",
138
+ googlePay: "auto",
139
+ },
140
+ };
172
141
  const howItWorksSteps = [
173
142
  {
174
143
  number: 1,
@@ -183,19 +152,20 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
183
152
  description: "After payment confirmation, your order will be processed and completed automatically",
184
153
  },
185
154
  ];
186
- return (_jsxs("div", { className: "relative mt-1 flex w-full flex-1 flex-col items-center justify-center", children: [_jsxs("form", { onSubmit: handleSubmit, className: "w-full space-y-6", children: [_jsx(OrderDetailsCollapsible, { order: order, dstToken: order.metadata.dstToken, tournament: order.type === "join_tournament" || order.type === "fund_tournament" ? order.metadata.tournament : undefined, nft: order.type === "mint_nft" ? order.metadata.nft : undefined, recipientName: recipientName, formattedExpectedDstAmount: formatTokenAmount(BigInt(order.srcAmount), order.metadata.dstToken.decimals), showTotal: true, totalAmount: amount ? `$${Number(amount).toFixed(2)}` : undefined }), _jsxs("div", { className: "w-full", children: [_jsx("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx("div", { className: "border-as-stroke mb-4 rounded-lg border p-3", children: _jsx(CardElement, { options: {}, onChange: (e) => setCardComplete(!!e.complete) }) }), _jsx(AddressElement, { options: {
155
+ return (_jsxs("div", { className: "relative mt-1 flex w-full flex-1 flex-col items-center justify-center", children: [_jsxs("form", { onSubmit: handleSubmit, className: "w-full space-y-6", children: [_jsx(OrderDetailsCollapsible, { order: order, dstToken: order.metadata.dstToken, tournament: order.type === "join_tournament" || order.type === "fund_tournament" ? order.metadata.tournament : undefined, nft: order.type === "mint_nft" ? order.metadata.nft : undefined, recipientName: recipientName, formattedExpectedDstAmount: formatTokenAmount(BigInt(order.srcAmount), order.metadata.dstToken.decimals), showTotal: true, totalAmount: amount ? `$${Number(amount).toFixed(2)}` : undefined }), _jsxs("div", { className: "w-full", children: [_jsx("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx(PaymentElement, { onChange: handlePaymentElementChange, options: stripeElementOptions }), showAddressElement && (_jsx(AddressElement, { options: {
187
156
  mode: "billing",
188
- fields: { phone: "always" },
189
- display: { name: "split" },
190
- validation: { phone: { required: "always" } },
191
- },
192
- // Ensure we mirror Stripe's built-in completeness signal and capture values
193
- onChange: (e) => {
194
- setAddressComplete(!!e.complete);
195
- setAddressValue(e.value);
196
- } })] }), message && (_jsxs("div", { className: "bg-as-red/10 border-as-red/20 flex w-full items-center gap-3 rounded-2xl border p-4", children: [_jsx("div", { className: "bg-as-red flex h-6 w-6 shrink-0 items-center justify-center rounded-full", children: _jsx("svg", { className: "h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsx("div", { className: "text-as-red text-sm font-medium", children: message })] })), _jsx(ShinyButton, { type: "submit", accentColor: "hsl(var(--as-brand))", disabled: !stripe || !elements || loading || !cardComplete || !addressComplete, className: "relative w-full py-4 text-lg font-semibold", children: loading ? (_jsxs("div", { className: "flex items-center justify-center gap-3", children: [_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-current border-t-transparent" }), _jsx("span", { className: "text-white", children: "Processing Payment..." })] })) : (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx("span", { className: "text-white", children: "Complete Payment" }), amount && _jsxs("span", { className: "text-white/90", children: ["$", Number(amount).toFixed(2)] })] })) })] }), showHowItWorks && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", onClick: e => {
197
- if (e.target === e.currentTarget) {
198
- markHowItWorksAsSeen();
199
- }
200
- }, children: _jsxs("div", { className: "bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6", children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsx("h2", { className: "text-as-primary text-xl font-semibold", children: "How it works" }), _jsx("button", { onClick: markHowItWorksAsSeen, className: "text-as-primary/60 hover:text-as-primary transition-colors", children: _jsx(X, { className: "h-6 w-6" }) })] }), _jsxs("div", { className: "space-y-6", children: [_jsx(PaymentMethodIcons, {}), _jsx(HowItWorks, { steps: howItWorksSteps })] })] }) }))] }));
157
+ fields: {
158
+ phone: "always",
159
+ },
160
+ // More granular control
161
+ display: {
162
+ name: "split", // or 'split' for first/last name separately
163
+ },
164
+ // Validation
165
+ validation: {
166
+ phone: {
167
+ required: "always", // or 'always', 'never'
168
+ },
169
+ },
170
+ } }))] }), message && (_jsxs("div", { className: "bg-as-red/10 border-as-red/20 flex w-full items-center gap-3 rounded-2xl border p-4", children: [_jsx("div", { className: "bg-as-red flex h-6 w-6 shrink-0 items-center justify-center rounded-full", children: _jsx("svg", { className: "h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsx("div", { className: "text-as-red text-sm font-medium", children: message })] })), _jsx(ShinyButton, { type: "submit", accentColor: "hsl(var(--as-brand))", disabled: !stripe || !elements || loading, className: "relative w-full py-4 text-lg font-semibold", children: loading ? (_jsxs("div", { className: "flex items-center justify-center gap-3", children: [_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-current border-t-transparent" }), _jsx("span", { className: "text-white", children: "Processing Payment..." })] })) : (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx("span", { className: "text-white", children: "Complete Payment" }), amount && _jsxs("span", { className: "text-white/90", children: ["$", Number(amount).toFixed(2)] })] })) })] }), showHowItWorks && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: _jsxs("div", { className: "bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6", children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsx("h2", { className: "text-as-primary text-xl font-semibold", children: "How it works" }), _jsx("button", { onClick: () => setShowHowItWorks(false), className: "text-as-primary/60 hover:text-as-primary transition-colors", children: _jsx(X, { className: "h-6 w-6" }) })] }), _jsxs("div", { className: "space-y-6", children: [_jsx(PaymentMethodIcons, {}), _jsx(HowItWorks, { steps: howItWorksSteps })] })] }) }))] }));
201
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.26-alpha.1",
3
+ "version": "0.0.26-alpha.2",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -4,10 +4,10 @@ import { components } from "@b3dotfun/sdk/anyspend/types/api";
4
4
  import { ShinyButton, useB3, useModalStore, useProfile } from "@b3dotfun/sdk/global-account/react";
5
5
  import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
6
6
  import { formatStripeAmount } from "@b3dotfun/sdk/shared/utils/payment.utils";
7
- import { AddressElement, CardElement, Elements, useElements, useStripe } from "@stripe/react-stripe-js";
8
- import { loadStripe, PaymentIntentResult, StripeCardElement } from "@stripe/stripe-js";
7
+ import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
8
+ import { loadStripe, PaymentIntentResult, StripePaymentElementOptions } from "@stripe/stripe-js";
9
9
  import { X } from "lucide-react";
10
- import { useCallback, useEffect, useState } from "react";
10
+ import { useEffect, useState } from "react";
11
11
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "../AnySpendFingerprintWrapper";
12
12
  import HowItWorks from "./HowItWorks";
13
13
  import PaymentMethodIcons from "./PaymentMethodIcons";
@@ -99,17 +99,7 @@ function StripePaymentForm({
99
99
  const [amount, setAmount] = useState<string | null>(null);
100
100
  const [stripeReady, setStripeReady] = useState<boolean>(false);
101
101
  const [showHowItWorks, setShowHowItWorks] = useState<boolean>(false);
102
- // Require-completion flags for Card & Address
103
- const [cardComplete, setCardComplete] = useState<boolean>(false);
104
- const [addressComplete, setAddressComplete] = useState<boolean>(false);
105
- // Snapshot of AddressElement value to pass to billing_details
106
- const [addressValue, setAddressValue] = useState<any>(null);
107
-
108
- // Helper function to mark "How it works" as seen
109
- const markHowItWorksAsSeen = useCallback(() => {
110
- setShowHowItWorks(false);
111
- localStorage.setItem("b3-payment-how-it-works-seen", "true");
112
- }, []);
102
+ const [showAddressElement, setShowAddressElement] = useState<boolean>(false);
113
103
 
114
104
  useEffect(() => {
115
105
  if (stripe && elements) {
@@ -117,22 +107,16 @@ function StripePaymentForm({
117
107
  }
118
108
  }, [stripe, elements, order.id]);
119
109
 
120
- // Check if user has seen "How it works" before
121
- useEffect(() => {
122
- const hasSeenHowItWorks = localStorage.getItem("b3-payment-how-it-works-seen");
123
- if (!hasSeenHowItWorks) {
124
- setShowHowItWorks(true);
125
- }
126
- }, []);
127
-
128
110
  useEffect(() => {
129
111
  const fetchPaymentIntent = async () => {
130
112
  if (!stripe || !clientSecret) return;
131
113
 
132
114
  try {
133
115
  const paymentIntent = await stripe.retrievePaymentIntent(clientSecret);
134
- const amt = paymentIntent.paymentIntent?.amount ? formatStripeAmount(paymentIntent.paymentIntent.amount) : null;
135
- setAmount(amt);
116
+ const amount = paymentIntent.paymentIntent?.amount
117
+ ? formatStripeAmount(paymentIntent.paymentIntent.amount)
118
+ : null;
119
+ setAmount(amount);
136
120
  } catch (error) {
137
121
  console.error("@@stripe-web2-payment:retrieve-intent-error:", JSON.stringify(error, null, 2));
138
122
  }
@@ -141,10 +125,16 @@ function StripePaymentForm({
141
125
  fetchPaymentIntent();
142
126
  }, [clientSecret, stripe]);
143
127
 
128
+ // Handle payment element changes
129
+ const handlePaymentElementChange = (event: any) => {
130
+ // Show address element only for card payments
131
+ setShowAddressElement(event.value.type === "card");
132
+ };
133
+
144
134
  const handleSubmit = async (e: React.FormEvent) => {
145
135
  e.preventDefault();
146
136
 
147
- if (!stripe || !elements || !clientSecret) {
137
+ if (!stripe || !elements) {
148
138
  setMessage("Stripe is not initialized");
149
139
  return;
150
140
  }
@@ -152,44 +142,10 @@ function StripePaymentForm({
152
142
  setLoading(true);
153
143
  setMessage(null);
154
144
 
155
- // Block submission until both card and billing address are complete
156
- if (!cardComplete || !addressComplete) {
157
- setMessage("Please complete all required billing address and card fields.");
158
- setLoading(false);
159
- return;
160
- }
161
-
162
145
  try {
163
- const card = elements.getElement(CardElement) as StripeCardElement | null;
164
- if (!card) {
165
- setMessage("Card element not found");
166
- setLoading(false);
167
- return;
168
- }
169
-
170
- const result = (await stripe.confirmCardPayment(clientSecret, {
171
- payment_method: {
172
- card,
173
- billing_details: {
174
- // Map AddressElement values into billing_details
175
- name: addressValue?.name
176
- ? typeof addressValue.name === "string"
177
- ? addressValue.name
178
- : [addressValue.name.firstName, addressValue.name.lastName].filter(Boolean).join(" ")
179
- : undefined,
180
- phone: addressValue?.phone || undefined,
181
- address: addressValue?.address
182
- ? {
183
- line1: addressValue.address.line1 || undefined,
184
- line2: addressValue.address.line2 || undefined,
185
- city: addressValue.address.city || undefined,
186
- state: addressValue.address.state || undefined, // province/region
187
- postal_code: addressValue.address.postal_code || undefined,
188
- country: addressValue.address.country || undefined,
189
- }
190
- : undefined,
191
- },
192
- },
146
+ const result = (await stripe.confirmPayment({
147
+ elements,
148
+ redirect: "if_required",
193
149
  })) as PaymentIntentResult;
194
150
 
195
151
  if (result.error) {
@@ -248,6 +204,17 @@ function StripePaymentForm({
248
204
  return <StripeLoadingState />;
249
205
  }
250
206
 
207
+ const stripeElementOptions: StripePaymentElementOptions = {
208
+ layout: "tabs" as const,
209
+ fields: {
210
+ billingDetails: "auto" as const,
211
+ },
212
+ wallets: {
213
+ applePay: "auto" as const,
214
+ googlePay: "auto" as const,
215
+ },
216
+ };
217
+
251
218
  const howItWorksSteps = [
252
219
  {
253
220
  number: 1,
@@ -279,28 +246,30 @@ function StripePaymentForm({
279
246
  totalAmount={amount ? `$${Number(amount).toFixed(2)}` : undefined}
280
247
  />
281
248
 
282
- {/* Simplified Payment Form (Card + LinkAuth) */}
249
+ {/* Simplified Payment Form */}
283
250
  <div className="w-full">
284
251
  <div className="text-as-primary mb-4 text-lg font-semibold">Payment Details</div>
285
-
286
- {/* Card input */}
287
- <div className="border-as-stroke mb-4 rounded-lg border p-3">
288
- <CardElement options={{}} onChange={(e: any) => setCardComplete(!!e.complete)} />
289
- </div>
290
-
291
- <AddressElement
292
- options={{
293
- mode: "billing",
294
- fields: { phone: "always" },
295
- display: { name: "split" },
296
- validation: { phone: { required: "always" } },
297
- }}
298
- // Ensure we mirror Stripe's built-in completeness signal and capture values
299
- onChange={(e: any) => {
300
- setAddressComplete(!!e.complete);
301
- setAddressValue(e.value);
302
- }}
303
- />
252
+ <PaymentElement onChange={handlePaymentElementChange} options={stripeElementOptions} />
253
+ {showAddressElement && (
254
+ <AddressElement
255
+ options={{
256
+ mode: "billing",
257
+ fields: {
258
+ phone: "always",
259
+ },
260
+ // More granular control
261
+ display: {
262
+ name: "split", // or 'split' for first/last name separately
263
+ },
264
+ // Validation
265
+ validation: {
266
+ phone: {
267
+ required: "always", // or 'always', 'never'
268
+ },
269
+ },
270
+ }}
271
+ />
272
+ )}
304
273
  </div>
305
274
 
306
275
  {/* Error Message */}
@@ -319,7 +288,7 @@ function StripePaymentForm({
319
288
  <ShinyButton
320
289
  type="submit"
321
290
  accentColor="hsl(var(--as-brand))"
322
- disabled={!stripe || !elements || loading || !cardComplete || !addressComplete}
291
+ disabled={!stripe || !elements || loading}
323
292
  className="relative w-full py-4 text-lg font-semibold"
324
293
  >
325
294
  {loading ? (
@@ -338,20 +307,13 @@ function StripePaymentForm({
338
307
 
339
308
  {/* How it works modal */}
340
309
  {showHowItWorks && (
341
- <div
342
- className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
343
- onClick={e => {
344
- if (e.target === e.currentTarget) {
345
- markHowItWorksAsSeen();
346
- }
347
- }}
348
- >
310
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
349
311
  <div className="bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6">
350
312
  {/* Modal header */}
351
313
  <div className="mb-6 flex items-center justify-between">
352
314
  <h2 className="text-as-primary text-xl font-semibold">How it works</h2>
353
315
  <button
354
- onClick={markHowItWorksAsSeen}
316
+ onClick={() => setShowHowItWorks(false)}
355
317
  className="text-as-primary/60 hover:text-as-primary transition-colors"
356
318
  >
357
319
  <X className="h-6 w-6" />