The checkout components render a two-panel layout: order summary on one side, payment on the other. They support crypto and fiat, custom form fields, shipping options, discount codes, and optional B3 workflow triggers. See it live.

Info

For the checkout sessions REST API (backend-driven, session-based flows), see Checkout Sessions. This page covers the React checkout components.

How it works

mermaid
flowchart LR A[Render Checkout] --> B{Has Form?} B -- Yes --> C[Collect Info] B -- No --> D[Choose Payment] C --> D D --> E{Crypto or Fiat?} E -- Crypto --> F[Connect Wallet & Pay] E -- Fiat --> G[Onramp Redirect] F --> H[Order Created] G --> H H --> I[Success Callback]

Quick start

1

Install the SDK

bash
npm install @b3dotfun/sdk
2

Import the component

tsx
import { AnySpendCheckout } from "@b3dotfun/sdk/anyspend/react";
3

Render the checkout

tsx
<AnySpendCheckout recipientAddress="0xMerchantAddress..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} items={[ { name: "Pro Plan - Monthly", description: "Unlimited access to all features", amount: "10000000", // 10 USDC (6 decimals) quantity: 1, }, ]} organizationName="Acme Inc" organizationLogo="/acme-logo.svg" themeColor="#4f46e5" onSuccess={(result) => { console.log("Payment complete:", result.orderId); }}/>;

Components

<AnySpendCheckout>

The main checkout component renders a two-panel layout with an order summary/cart panel and a payment panel supporting crypto and fiat options. Optionally includes a form panel for collecting customer information, shipping selection, and discount codes.

Core props

recipientAddressstringrequiredpath

Merchant wallet address to receive payment

destinationTokenAddressstringrequiredpath

Token contract address for settlement (e.g., USDC)

destinationTokenChainIdnumberrequiredpath

Chain ID for settlement (e.g., 8453 for Base)

itemsCheckoutItem[]requiredpath

Line items displayed in the cart panel

Branding

organizationNamestringpath

Merchant name shown in the checkout header

stringpath

URL for the merchant logo

themeColorstringpath

Hex color for theming (e.g., "#4f46e5")

buttonTextstringpath

Custom text for the payment button

Order summary

totalAmountstringpath

Override the computed total in wei. Use when the total differs from the sum of item amounts (e.g., after discounts or fees).

shippingstring | { amount: string; label?: string }path

Shipping cost. Pass a string for amount in wei, or an object with a custom label.

taxstring | { amount: string; label?: string; rate?: string }path

Tax amount. Pass a string for amount in wei, or an object with label and optional rate display (e.g., "8.5%").

discountstring | { amount: string; label?: string; code?: string }path

Discount amount (displayed as a deduction). Pass a string for amount in wei, or an object with label and optional code.

summaryLinesCheckoutSummaryLine[]path

Additional summary line items like platform fees, tips, or service charges

Payment

defaultPaymentMethodPaymentMethodpath

Which payment method to expand initially. Options: "crypto", "coinbase", "stripe".

senderAddressstringpath

Pre-fill the sender address to show token balances before wallet connection

checkoutSessionIdstringpath

Link this checkout to a backend checkout session for tracking

Callbacks

onSuccess(result: { txHash?: string; orderId?: string }) => voidpath

Called on successful payment

onError(error: Error) => voidpath

Called on payment error

returnUrlstringpath

URL to redirect to after payment completion

returnLabelstringpath

Label for the return/redirect button

Display options

mode'page' | 'embedded'pathdefault: 'page'

page for standalone, embedded for inline within your layout

showPointsbooleanpathdefault: false

Show points earned in the order status summary

showOrderIdbooleanpathdefault: false

Show the order ID in the order status summary

footerReactNode | nullpath

Custom footer for the order summary. Pass null to hide the default "Powered by" footer.

Customization

slotsAnySpendSlotspath

Replace UI sections. See Customization.

contentAnySpendContentpath

Override text/messages. See Customization.

themeAnySpendThemepath

Configure colors. See Customization.

classesAnySpendCheckoutClassespath

CSS class overrides. See Customization.

Custom forms

Collect customer information during checkout using a JSON schema or a custom React component.

formSchemaCheckoutFormSchemapath

JSON schema defining fields to collect from the customer (email, name, address, etc.). See CheckoutFormSchema below.

formComponentReact.ComponentType<CheckoutFormComponentProps>path

Custom React component to render as the checkout form. Use this when formSchema isn't flexible enough.

onFormSubmit(data: Record<string, unknown>) => voidpath

Called when form data changes. Form data is also automatically included in the order's callbackMetadata.

Shipping options

shippingOptionsShippingOption[]path

Array of shipping options to display as a radio-button selector. The selected option's amount is automatically added to the order total.

When true, renders a shipping address form (street, city, state, zip, country). The address is included in the order's callbackMetadata.

onShippingChange(option: ShippingOption) => voidpath

Called when the user selects a shipping option

Discount codes

enableDiscountCodebooleanpath

Show a discount code input field. Requires validateDiscount to be set.

validateDiscount(code: string) => Promise<DiscountResult>path

Async function to validate a discount code against your backend. Returns a DiscountResult with the discount amount. The validated discount is automatically applied to the order total.

onDiscountApplied(result: DiscountResult) => voidpath

Called when a valid discount code is applied


<AnySpendCheckoutTrigger>

Extends AnySpendCheckout with B3 workflow integration. When a user completes payment, a B3 workflow is automatically triggered with the payment data and any custom metadata.

tsx
import { AnySpendCheckoutTrigger } from "@b3dotfun/sdk/anyspend/react";<AnySpendCheckoutTrigger recipientAddress="0xMerchantAddress..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} items={[ { name: "Pro Plan", amount: "10000000", quantity: 1 }, ]} workflowId="wf_provision_subscription" orgId="org_acme" callbackMetadata={{ inputs: { plan: "pro", userId: "user_123", email: "user@example.com", }, }} onSuccess={(result) => console.log("Workflow triggered:", result)}/>;

Workflow props

All <AnySpendCheckout> props are supported, plus:

workflowIdstringpath

B3 workflow ID to trigger on successful payment

orgIdstringpath

Organization ID that owns the workflow

callbackMetadataobjectpath

Metadata merged into the order. The inputs field is accessible in workflows via {{root.result.inputs.*}}.

itemsCheckoutItem[]path

Optional for AnySpendCheckoutTrigger — if omitted, only the payment panel is shown (no cart).

totalAmountstringpath

Required when items is not provided (since there's nothing to compute a total from).


Types

CheckoutItem

Each item in the checkout cart:

typescript
interface CheckoutItem { /** Unique identifier for the item */ id?: string; /** Item name */ name: string; /** Short description */ description?: string; /** Item image URL */ imageUrl?: string; /** Price in wei (smallest unit of destination token) */ amount: string; /** Quantity */ quantity: number; /** Custom metadata displayed as label: value pairs (e.g., { "Size": "Large" }) */ metadata?: Record<string, string>;}

CheckoutSummaryLine

Additional line items in the order summary:

typescript
interface CheckoutSummaryLine { /** Display label (e.g., "Platform Fee", "Tip") */ label: string; /** Amount in wei. Negative values are shown as deductions. */ amount: string; /** Optional description or note */ description?: string;}

CheckoutFormSchema

Define custom form fields using a JSON schema:

typescript
interface CheckoutFormSchema { fields: CheckoutFormField[];}interface CheckoutFormField { /** Unique field identifier */ id: string; /** Field type */ type: "text" | "email" | "phone" | "textarea" | "select" | "number" | "checkbox" | "address"; /** Display label */ label: string; /** Placeholder text */ placeholder?: string; /** Whether the field is required */ required?: boolean; /** Default value */ defaultValue?: string; /** Options for "select" type fields */ options?: { label: string; value: string }[]; /** Validation rules */ validation?: { pattern?: string; minLength?: number; maxLength?: number; min?: number; max?: number; };}
Tip

The "address" field type renders a full address form (street, city, state, zip, country) automatically — no need to define each sub-field.

ShippingOption

typescript
interface ShippingOption { /** Unique option identifier */ id: string; /** Display name (e.g., "Standard Shipping") */ name: string; /** Optional description */ description?: string; /** Cost in wei */ amount: string; /** Estimated delivery time (e.g., "5-7 business days") */ estimated_days?: string;}

DiscountResult

Returned by your validateDiscount function:

typescript
interface DiscountResult { /** Whether the code is valid */ valid: boolean; /** Discount type */ discount_type?: "percentage" | "fixed"; /** The discount value (e.g., "10" for 10%) */ discount_value?: string; /** Computed discount amount in wei */ discount_amount?: string; /** Final amount after discount in wei */ final_amount?: string; /** Error message if invalid */ error?: string;}

AddressData

Structure for collected shipping addresses:

typescript
interface AddressData { street: string; city: string; state: string; zip: string; country: string;}

CheckoutFormComponentProps

Props passed to custom form components (via formComponent or the checkoutForm slot):

typescript
interface CheckoutFormComponentProps { /** Call when form values change */ onSubmit: (data: Record<string, unknown>) => void; /** Signal whether the form is valid */ onValidationChange: (isValid: boolean) => void; /** Current form data */ formData: Record<string, unknown>; /** Update form data */ setFormData: (data: Record<string, unknown>) => void;}

Examples

E-commerce store

tsx
function CheckoutPage({ cart, shippingAddress }) { const subtotal = cart.reduce( (sum, item) => sum + BigInt(item.amount) * BigInt(item.quantity), 0n ); return ( <AnySpendCheckout mode="page" recipientAddress="0xMerchantWallet..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} // Cart items items={cart.map((item) => ({ name: item.name, description: item.variant, imageUrl: item.imageUrl, amount: item.amount, quantity: item.quantity, metadata: { "Size": item.size, "Color": item.color, }, }))} // Order summary shipping={{ amount: "2000000", label: "Standard Shipping" }} tax={{ amount: "850000", label: "Sales Tax", rate: "8.5%" }} discount={{ amount: "5000000", label: "Welcome Discount", code: "WELCOME10" }} summaryLines={[ { label: "Platform Fee", amount: "100000" }, ]} // Branding organizationName="Acme Store" organizationLogo="/acme-logo.svg" themeColor="#4f46e5" buttonText="Complete Purchase" // Callbacks onSuccess={(result) => { createOrder({ orderId: result.orderId, txHash: result.txHash, items: cart, shippingAddress, }); router.push("/order-confirmation"); }} onError={(error) => { toast.error("Payment failed: " + error.message); }} returnUrl="/shop" returnLabel="Continue Shopping" // Customization content={{ successTitle: "Order Placed!", successDescription: "Check your email for order confirmation and tracking.", }} /> );}

SaaS subscription

tsx
function SubscriptionCheckout({ plan }) { return ( <AnySpendCheckoutTrigger recipientAddress="0xTreasuryAddress..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} items={[ { name: `${plan.name} Plan`, description: `${plan.billingCycle} billing`, amount: plan.amountWei, quantity: 1, metadata: { "Billing": plan.billingCycle, "Users": `${plan.maxUsers} seats`, }, }, ]} organizationName="SaaS Co" themeColor="#059669" // Workflow integration workflowId="wf_activate_subscription" orgId="org_saas" callbackMetadata={{ inputs: { planId: plan.id, billingCycle: plan.billingCycle, maxUsers: plan.maxUsers, }, }} onSuccess={() => { toast.success("Subscription activated!"); router.push("/dashboard"); }} content={{ successTitle: "Welcome to " + plan.name + "!", successDescription: "Your subscription is now active. Head to your dashboard to get started.", returnButtonLabel: "Go to Dashboard", }} /> );}

Simple payment (no cart)

tsx
// Use AnySpendCheckoutTrigger without items for a simple payment flow<AnySpendCheckoutTrigger recipientAddress="0x..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} totalAmount="25000000" // 25 USDC organizationName="Service Provider" buttonText="Pay $25" workflowId="wf_process_payment" orgId="org_provider" onSuccess={(result) => console.log("Paid:", result)}/>

Checkout with custom forms

Collect customer info, offer shipping options, and validate discount codes — all integrated into the checkout flow:

tsx
function FullCheckout({ cart }) { return ( <AnySpendCheckout mode="page" recipientAddress="0xMerchantWallet..." destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} items={cart} organizationName="Acme Store" // Collect customer information formSchema={{ fields: [ { id: "email", type: "email", label: "Email", placeholder: "you@example.com", required: true }, { id: "name", type: "text", label: "Full Name", placeholder: "Jane Doe", required: true }, { id: "phone", type: "phone", label: "Phone", placeholder: "+1 555-1234" }, { id: "size", type: "select", label: "T-Shirt Size", options: [ { label: "Small", value: "S" }, { label: "Medium", value: "M" }, { label: "Large", value: "L" }, { label: "X-Large", value: "XL" }, ], }, { id: "notes", type: "textarea", label: "Order Notes", placeholder: "Any special instructions?" }, ], }} // Shipping address collection collectShippingAddress // Shipping method selection shippingOptions={[ { id: "standard", name: "Standard Shipping", amount: "2000000", estimated_days: "5-7 business days" }, { id: "express", name: "Express Shipping", amount: "5000000", estimated_days: "2-3 business days" }, { id: "overnight", name: "Overnight", amount: "10000000", estimated_days: "Next business day" }, ]} // Discount codes enableDiscountCode validateDiscount={async (code) => { const res = await fetch(`/api/discounts/validate?code=${code}`); return res.json(); }} // Callbacks onFormSubmit={(data) => console.log("Form data:", data)} onShippingChange={(option) => console.log("Shipping:", option.name)} onDiscountApplied={(result) => console.log("Discount:", result)} onSuccess={(result) => router.push("/order-confirmation")} /> );}

Checkout with custom form component

Use a fully custom form component when the JSON schema isn't flexible enough:

tsx
function MyCustomForm({ formData, setFormData, onValidationChange }) { const [errors, setErrors] = useState({}); useEffect(() => { onValidationChange(!!formData.email && !!formData.agree); }, [formData]); return ( <div className="space-y-4"> <input type="email" value={formData.email || ""} onChange={(e) => setFormData({ ...formData, email: e.target.value })} placeholder="Email address" className="w-full border rounded-lg p-2" /> <label className="flex items-center gap-2"> <input type="checkbox" checked={!!formData.agree} onChange={(e) => setFormData({ ...formData, agree: e.target.checked })} /> I agree to the terms of service </label> </div> );}// Usage<AnySpendCheckout {...checkoutProps} formComponent={MyCustomForm} onFormSubmit={(data) => console.log("Custom form data:", data)}/>

Donation / tip jar

tsx
function DonationPage({ creator }) { const [amount, setAmount] = useState("5000000"); // 5 USDC default return ( <AnySpendCheckout mode="embedded" recipientAddress={creator.walletAddress} destinationTokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" destinationTokenChainId={8453} items={[ { name: `Support ${creator.name}`, description: "Thank you for your support!", imageUrl: creator.avatarUrl, amount, quantity: 1, }, ]} footer={null} // Hide default footer content={{ successTitle: "Thank you!", successDescription: `${creator.name} appreciates your support.`, }} theme={{ brandColor: "#ec4899" }} /> );}

Next steps

Checkout Sessions API

Backend-driven checkout sessions with REST API

Learn More
Customization

Customize checkout with slots, content, themes, and CSS

Learn More
Error Handling

Handle payment errors gracefully

Learn More