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

# Basis Theory checkout

> Card data is tokenized in a PCI DSS Level 1 vault and never touches your servers or Macropay's

When a customer types their card number into Macropay checkout, those digits
go straight into a sandboxed, cross-origin iframe and are tokenized inside a
**PCI DSS Level 1 compliant** vault. The card number, expiry, and CVC never
reach your origin, your backend, or Macropay's. What you and the acquirer see
is a non-sensitive token — never the raw PAN.

<Note>
  This is the same capture layer whether you ship a hosted
  [Checkout Link](/features/checkout/links), drop in the
  [embedded checkout](/features/checkout/embed), or build a custom UI on
  [Checkout Sessions](/features/checkout/session). One iframe, one PCI
  posture, three integration styles.
</Note>

## Why tokenization in a vault matters

The card form lives in iframes served from a vault domain, not from your page.
Every keystroke is intercepted there, exchanged for a token, and only the token
crosses back to Macropay to authorize the charge. The practical payoff:

| Benefit                | What it means for you                                                                                                                                                                                     |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Smallest PCI scope** | Sensitive card data never lands on your infrastructure, so your business qualifies for the lightest self-assessment tier.                                                                                 |
| **Nothing to leak**    | No PAN in your logs, error tracker, analytics, or database — there's no card data on your side to spill in the first place.                                                                               |
| **Merchant of Record** | Macropay charges the card as the seller of record on its own acquirer relationships. You don't bring a processor, pass your own PCI audit, or remit sales tax/VAT — we do, and we cap your tax liability. |

<Info>
  Because Macropay is the Merchant of Record, the PCI burden, tax remittance,
  and chargeback handling all sit with us. Tokenization is the mechanism; MoR
  is the coverage.
</Info>

## What gets captured

The form renders three independent fields, each in its own sandboxed iframe:

1. **Card number** — with live brand detection. A row of brand icons on the
   right edge lights up the detected network as the customer types.
2. **Expiry** (MM/YY)
3. **CVC**

Splitting the form into separate iframes keeps each field individually
focusable, tabbable, and validatable, while every field stays cross-origin
from your page. Tokenization runs as a single vault call on submit; Macropay
receives the resulting token and nothing else.

## Embedding the form

How much you do depends on which surface you choose:

<CardGroup cols={2}>
  <Card title="Hosted link or embed" icon="bolt">
    Using a [Checkout Link](/features/checkout/links) or the
    [embedded checkout](/features/checkout/embed)? The vault iframes are
    already inside Macropay's hosted checkout. Nothing to wire up.
  </Card>

  <Card title="Your own UI" icon="code">
    Building on [Checkout Sessions](/features/checkout/session)? Mount the
    `<CheckoutForm />` React component from `@macropayments/checkout` and the
    card form appears, fully orchestrated.
  </Card>
</CardGroup>

For a custom UI, the component handles iframe orchestration, inline
validation, and tokenization for you:

```tsx theme={null}
import { CheckoutForm } from '@macropayments/checkout/react'

<CheckoutForm
  checkout={checkoutSession}
  theme="light"
  onConfirmed={(order) => router.push(`/receipt/${order.id}`)}
/>
```

You wire up `onConfirmed` to route the buyer after the charge clears. You
never receive — or have to protect — the raw card data.

## Theming the fields

Vault iframes don't accept style updates after they mount, so a theme switch
re-mounts them in the new color scheme. Macropay watches the
`<html class="dark">` toggle through a debounced `MutationObserver` and does
the re-mount for you.

<Warning>
  Macropay's hosted checkout (links + embed) renders **light-mode only** by
  design. The card fields stay on a crisp light surface for every merchant,
  even if you set `data-macropay-checkout-theme="dark"` on a link — the rest
  of the page goes dark, the card form stays light. This keeps brand-icon
  contrast and field affordances correct across every device and OS theme.

  Building your own UI on Checkout Sessions? You can opt the card fields into a
  dark theme — just know you own the contrast for the brand-icon row and the
  field error states.
</Warning>

Inside the form, the visual states are tuned to sit pixel-aligned next to your
own inputs:

* **Interaction states** — focus, hover, error, and disabled are driven by
  stateful classes on the wrapper, not `:focus` selectors. The iframe takes
  focus, so the wrapper reflects it.
* **Borders and rings** — border color, ring width, and transitions match
  Macropay's `Input` atom, so the card fields line up beside the Cardholder
  Name input.
* **Brand row** — non-matching brands fade to 0.25 opacity while the detected
  brand pops to full opacity.

## Supported card brands

The on-screen brand row shows live indicators for the four most common
networks: **Visa, Mastercard, American Express, and Discover.** Tokenization
also covers Diners Club, JCB, UnionPay, Maestro, Hipercard, and Elo — buyers
can pay with any of those even though the icon row stays focused on the major
four.

Which cards actually clear is governed by Macropay's acquirer and the buyer's
billing country, not by the iframe.

## Wallets

When the browser supports them, **Apple Pay** and **Google Pay** appear
**above** the card form. The wallet sheet skips the card iframes entirely:
Apple or Google hands back a tokenized payment method that Macropay relays
straight to the acquirer.

<Tip>
  Wallets keep working even if the card vault is unreachable, because they
  route through the browser rather than the iframe. For embedded checkout,
  wallets need a one-time domain validation — see
  [enabling wallet payment methods](/features/checkout/embed#enabling-wallet-payment-methods-apple-pay-google-pay-etc).
</Tip>

## FAQ

<AccordionGroup>
  <Accordion title="How far can I restyle the card form?">
    The visible chrome — border, padding, radius, shadow — is yours to re-skin
    with Tailwind classes on the wrapper. The text *inside* the iframe (font
    family, size, color) is limited to a small set of style tokens the vault
    exposes, which Macropay tunes to match the wrapper. Open a support request
    if you need a non-default token.
  </Accordion>

  <Accordion title="Why three iframes instead of one?">
    Split fields let each input be tabbable, focusable, and validated on its
    own while keeping every keystroke cross-origin from your page. One field,
    one iframe, one isolation boundary.
  </Accordion>

  <Accordion title="Is the form accessible?">
    Yes. Each iframe is labelled and exposes its validation state to assistive
    tech. Errors render below a field only after blur, and only when the field
    is non-empty and invalid — no shouting while the customer is still typing.
  </Accordion>

  <Accordion title="What if the card vault is unavailable?">
    The customer sees a "card capture is unavailable" message and the submit
    button stays disabled. Wallets (Apple Pay / Google Pay) still work because
    they go through the browser. Macropay monitors vault uptime and posts any
    incidents to its status page.
  </Accordion>
</AccordionGroup>
