> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyspend.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout

Checkout components with cart, order summary, forms, shipping, discounts, and crypto/fiat payment

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](https://sdk-demo.anyspend.com/).

For the checkout **sessions REST API** (backend-driven, session-based flows), see [Checkout Sessions](/anyspend/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

**Install the SDK**

```bash
    npm install @b3dotfun/sdk
    ```
  **Import the component**

```tsx
    import { AnySpendCheckout } from "@b3dotfun/sdk/anyspend/react";
    ```
  **Render the checkout**

```tsx title="Basic Checkout" icon="cart-shopping"
    <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

Merchant wallet address to receive payment

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

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

Line items displayed in the cart panel

#### Branding

Merchant name shown in the checkout header

URL for the merchant logo

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

Custom text for the payment button

#### Order summary

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

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

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

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

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

#### Payment

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

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

Link this checkout to a backend checkout session for tracking

#### Callbacks

Called on successful payment

Called on payment error

URL to redirect to after payment completion

Label for the return/redirect button

#### Display options

`page` for standalone, `embedded` for inline within your layout

Show points earned in the order status summary

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

Replace UI sections. See [Customization](/anyspend/customization#slots).

Override text/messages. See [Customization](/anyspend/customization#content).

Configure colors. See [Customization](/anyspend/customization#theme).

CSS class overrides. See [Customization](/anyspend/customization#css-class-overrides).

#### Custom forms

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

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

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

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

#### Shipping options

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`.

Called when the user selects a shipping option

#### Discount codes

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

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.

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 title="Checkout with Workflow" icon="bolt"
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:

B3 workflow ID to trigger on successful payment

Organization ID that owns the workflow

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

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

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

---

## Types

### CheckoutItem

Each item in the checkout cart:

```typescript title="CheckoutItem" icon="code"
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 title="CheckoutSummaryLine" icon="code"
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 title="CheckoutFormSchema" icon="code"
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

```typescript title="ShippingOption" icon="code"
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 title="DiscountResult" icon="code"
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 title="AddressData" icon="code"
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 title="CheckoutFormComponentProps" icon="code"
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 title="Full E-Commerce Checkout" icon="store"
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 title="Subscription Checkout" icon="credit-card"
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 title="Payment-Only Checkout" icon="money-bill"
// 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 title="Full-Featured Checkout" icon="file-lines"
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 title="Custom Form Component" icon="puzzle-piece"
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 title="Donation Checkout" icon="heart"
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](/anyspend/checkout-sessions)**

Backend-driven checkout sessions with REST API

**[Customization](/anyspend/customization)**

Customize checkout with slots, content, themes, and CSS

**[Error Handling](/anyspend/error-handling)**

Handle payment errors gracefully