# Billing details

The details printed on invoices, as a form: billing email, the name and tax ID on invoices, and the postal address. There is no card field: cards and payment methods belong at the billing provider's portal (Polar, Stripe), which `invoice-history` and `subscription-status` link to. Errors from your server show under the field they concern; Save stays locked, saying which field is missing, until the required ones are filled. "Saved" shows once a save resolves, until the next edit.

- Registry name: `billing-details` (`@candor/billing-details`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/billing-details

## Install

```bash
npx candor-ui add billing-details
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/billing-details`.
It brings in: button, combobox, heading-level, heading-scope, input, locale, locked, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { BillingDetails, type BillingField } from "@/components/candor/billing-details"

export default function Example() {
  const [errors, setErrors] = React.useState<Partial<Record<BillingField, string>>>()
  return (
    <BillingDetails
      className="max-w-lg"
      taxIdLabel="VAT number"
      countries={[
        { code: "GB", name: "United Kingdom" },
        { code: "IE", name: "Ireland" },
        { code: "US", name: "United States" },
      ]}
      value={{ email: "accounts@keel.example", name: "Keel Ltd", line1: "1 Harbour Street", city: "Bristol", postalCode: "BS1 4AA", country: "GB" }}
      errors={errors}
      onSave={async (d) => {
        await new Promise((r) => setTimeout(r, 400))
        if (d.taxId && !/^GB\d{9}$/.test(d.taxId)) {
          setErrors({ taxId: "That isn’t a UK VAT number: GB and nine digits." })
          throw new Error("invalid")
        }
        setErrors(undefined)
      }}
    />
  )
}
```

## <BillingDetails>

Props:

- `value?: Partial<BillingDetailsValue> | undefined`: The details on file. Without it the form starts empty.
- `onSave: (details: BillingDetailsValue) => void | Promise<void>`: Called with the details; the button waits on a returned promise and shows Saved when it resolves.
- `countries?: { code: string; name: string }[] | undefined`: Countries to choose from, by ISO code and name. Without it, country is a text field.
- `taxIdLabel?: string | undefined`: What the tax ID is called where the account is, e.g. "VAT number" or "GSTIN". Defaults to "Tax ID".
- `errors?: Partial<Record<BillingField, string>> | undefined`: Your server's errors, by field, shown under each field.
- `error?: string | undefined`: An error that isn't about one field, in the app's words.
- `busy?: boolean | undefined` (default `false`): The app is saving: the button says so.
- `className?: string | undefined`
- `headingLevel?: HeadingLevel | undefined`: The level of the form's heading, so it fits under the page's own headings. Default: one below the nearest `HeadingScope`, else 2 (`<h2>`).

## Strings (`billingDetails`)

The fixed words, English by default. Override any of them under `billingDetails` in `<LocaleProvider strings={{ billingDetails: { … } }}>`; the table is exported as `billingDetailsStrings`.

- `lockedSaving`: Saving the details
- `heading`: Billing details
- `intro`: What appears on your invoices. Cards and payment methods are managed with the billing provider, not here.
- `email`: Billing email
- `emailHint`: Invoices and payment receipts go here.
- `name`: Name on invoices
- `namePlaceholder`: Company or person
- `taxId`: Tax ID
- `optional`: {label} (optional)
- `address`: Address
- `line1`: Address line 1
- `line2`: Address line 2
- `city`: City
- `region`: State, province or region
- `postalCode`: Postal code
- `country`: Country or region
- `countryPlaceholder`: Choose a country or region
- `needs`: Fill in {label}.
- `save`: Save details
- `saving`: Saving…
- `saved`: Saved
- `saveFailed`: Saving the details failed.

## Types

### BillingDetailsValue

The details printed on invoices.

```ts
{
  email: string;
  name: string;
  taxId?: string | undefined;
  line1: string;
  line2?: string | undefined;
  city: string;
  region?: string | undefined;
  postalCode?: string | undefined;
  /** An ISO 3166 code ("GB") when `countries` is given, else what the person typed. */
  country: string;
}
```

### BillingField

```ts
keyof BillingDetailsValue
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### HeadingLevel

Import: `import type { HeadingLevel } from "@/lib/candor/heading-level"` (item `heading-level`, https://candor.themethodic.com/docs/heading-level.md)

A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.

```ts
1 | 2 | 3 | 4 | 5 | 6
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
