Skip to main content
The AnySpend checkout components provide a complete payment experience with cart display, order summaries, multiple payment methods, custom form collection, shipping options, discount codes, and optional workflow integration.
For the checkout sessions REST API (backend-driven, session-based flows), see Checkout Sessions. This page covers the React checkout components.

How It Works

Quick Start

1

Install the SDK

npm install @b3dotfun/sdk
2

Import the component

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

Render the checkout

Basic Checkout
<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

recipientAddress
string
required
Merchant wallet address to receive payment
destinationTokenAddress
string
required
Token contract address for settlement (e.g., USDC)
destinationTokenChainId
number
required
Chain ID for settlement (e.g., 8453 for Base)
items
CheckoutItem[]
required
Line items displayed in the cart panel

Branding

organizationName
string
Merchant name shown in the checkout header
URL for the merchant logo
themeColor
string
Hex color for theming (e.g., "#4f46e5")
buttonText
string
Custom text for the payment button

Order Summary

totalAmount
string
Override the computed total in wei. Use when the total differs from the sum of item amounts (e.g., after discounts or fees).
shipping
string | { amount: string; label?: string }
Shipping cost. Pass a string for amount in wei, or an object with a custom label.
tax
string | { amount: string; label?: string; rate?: string }
Tax amount. Pass a string for amount in wei, or an object with label and optional rate display (e.g., "8.5%").
discount
string | { amount: string; label?: string; code?: string }
Discount amount (displayed as a deduction). Pass a string for amount in wei, or an object with label and optional code.
summaryLines
CheckoutSummaryLine[]
Additional summary line items like platform fees, tips, or service charges

Payment

defaultPaymentMethod
PaymentMethod
Which payment method to expand initially. Options: "crypto", "coinbase", "stripe".
senderAddress
string
Pre-fill the sender address to show token balances before wallet connection
checkoutSessionId
string
Link this checkout to a backend checkout session for tracking

Callbacks

onSuccess
(result: { txHash?: string; orderId?: string }) => void
Called on successful payment
onError
(error: Error) => void
Called on payment error
returnUrl
string
URL to redirect to after payment completion
returnLabel
string
Label for the return/redirect button

Display Options

mode
'page' | 'embedded'
default:"'page'"
page for standalone, embedded for inline within your layout
showPoints
boolean
default:"false"
Show points earned in the order status summary
showOrderId
boolean
default:"false"
Show the order ID in the order status summary
Custom footer for the order summary. Pass null to hide the default “Powered by” footer.

Customization

slots
AnySpendSlots
Replace UI sections. See Customization.
content
AnySpendContent
Override text/messages. See Customization.
theme
AnySpendTheme
Configure colors. See Customization.
classes
AnySpendCheckoutClasses
CSS class overrides. See Customization.

Custom Forms

Collect customer information during checkout using a JSON schema or a custom React component.
formSchema
CheckoutFormSchema
JSON schema defining fields to collect from the customer (email, name, address, etc.). See CheckoutFormSchema below.
formComponent
React.ComponentType<CheckoutFormComponentProps>
Custom React component to render as the checkout form. Use this when formSchema isn’t flexible enough.
onFormSubmit
(data: Record<string, unknown>) => void
Called when form data changes. Form data is also automatically included in the order’s callbackMetadata.

Shipping Options

shippingOptions
ShippingOption[]
Array of shipping options to display as a radio-button selector. The selected option’s amount is automatically added to the order total.
collectShippingAddress
boolean
When true, renders a shipping address form (street, city, state, zip, country). The address is included in the order’s callbackMetadata.
onShippingChange
(option: ShippingOption) => void
Called when the user selects a shipping option

Discount Codes

enableDiscountCode
boolean
Show a discount code input field. Requires validateDiscount to be set.
validateDiscount
(code: string) => Promise<DiscountResult>
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) => void
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.
Checkout with Workflow
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:
workflowId
string
B3 workflow ID to trigger on successful payment
orgId
string
Organization ID that owns the workflow
callbackMetadata
object
Metadata merged into the order. The inputs field is accessible in workflows via {{root.result.inputs.*}}.
items
CheckoutItem[]
Optional for AnySpendCheckoutTrigger — if omitted, only the payment panel is shown (no cart).
totalAmount
string
Required when items is not provided (since there’s nothing to compute a total from).

Types

CheckoutItem

Each item in the checkout cart:
CheckoutItem
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:
CheckoutSummaryLine
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:
CheckoutFormSchema
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;
  };
}
The "address" field type renders a full address form (street, city, state, zip, country) automatically — no need to define each sub-field.

ShippingOption

ShippingOption
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:
DiscountResult
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:
AddressData
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):
CheckoutFormComponentProps
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

Full E-Commerce Checkout
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

Subscription Checkout
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)

Payment-Only Checkout
// 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:
Full-Featured Checkout
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:
Custom Form Component
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

Donation Checkout
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