## Tender Checkout (S—006)

A card payment form with a live card that flips to show the CVC, brand detection, Luhn checks and a paced pay button.

- URL: https://sundaeui.dev/components/tender-checkout
- Category: Checkout & payments · Art direction: Refined
- Access: Sundae Pro (authenticated install)
- Stack: React 18+, TypeScript, plain CSS; dependencies: none
- Install: `npx shadcn@latest add https://sundaeui.dev/r/tender-checkout.json`
- Tags: form, payment, animated, checkout, react, credit-card, validation

The card on the left mirrors what you type. Digits drop into place, the brand mark appears as soon as the number identifies it, and the card turns over to show the signature strip when you reach the security code. The design is quietly premium: a graphite face, a fine guilloché line pattern and a gold chip, with no glossy gradients.
Formatting and validation run as you type. Numbers group by brand (4-6-5 for Amex), the Luhn checksum is verified, expiry dates reject past months, and focus moves forward once a field is complete. Errors appear on blur, never while someone is still typing.
The pay button shows three steps: a progress fill while processing, then a stroke-drawn check on success, or an inline error that keeps the form editable. This component is the interface only. In production, put your provider’s hosted card fields in the same layout (for PCI SAQ-A scope) and keep the states.

### Usage

```tsx
import { TenderCheckout } from '@/components/sundae/tender-checkout'

<TenderCheckout
  amount={4800}
  currency="USD"
  description="Field notebook, set of 3"
  onPay={async (card) => {
    const res = await fetch('/api/pay', { method: 'POST', body: JSON.stringify({ token: await tokenize(card) }) })
    if (!res.ok) throw new Error('Your card was declined.')
  }}
/>
```

### Props

- `amount` (`number`, required): Amount in minor units, for example 4800 for $48.00.
- `currency` (`string`, default `'USD'`): ISO 4217 currency code.
- `locale` (`string`): Locale for price formatting. Defaults to the browser locale.
- `description` (`string`): Order summary line shown above the fields.
- `onPay` (`(card: CardDetails) => Promise<void>`, required): Called with validated details. Resolve for success, or throw an Error to show its message.
- `submitLabel` (`string`): Overrides “Pay $48.00”.
- `note` (`string`): Helper text under the button in the idle state.

### Accessibility

- Every field is a labelled native input with the right autocomplete tokens (cc-number, cc-exp, cc-csc, cc-name), so browser and password-manager autofill work.
- Errors are linked with aria-describedby and appear on blur, not on every keystroke.
- Payment status is announced through a polite live region. The decorative card is aria-hidden.
- With prefers-reduced-motion, the flip and progress animations are instant.
