# Invoice history

The account's invoices: number, when each was issued, the period it covers, the amount in its own currency, its status (an open or overdue one says when it's due), and a download your app serves. Candor never builds the PDF or takes payment: pass `portal` and the footer points at the billing provider's own portal (Polar, Stripe, Paddle) for cards and payment methods.

- Registry name: `invoice-history` (`@candor/invoice-history`)
- 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/invoice-history

## Install

```bash
npx candor-ui add invoice-history
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/invoice-history`.
It brings in: badge, data, locale, ops, page-state, table, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import { InvoiceHistory } from "@/components/invoice-history"

export default function Example() {
  return (
    <InvoiceHistory
      className="max-w-3xl"
      portal={{ provider: "Stripe", href: "#billing-portal" }}
      invoices={[
        { id: "i4", number: "INV-2026-0011", issuedAt: "2026-11-01", periodStart: "2026-11-01", periodEnd: "2026-11-30", amount: 588, currency: "USD", status: "open", dueAt: "2026-11-15" },
        { id: "i3", number: "INV-2026-0010", issuedAt: "2026-10-01", periodStart: "2026-10-01", periodEnd: "2026-10-31", amount: 588, currency: "USD", status: "paid", url: "#invoice-0010.pdf" },
        { id: "i2", number: "INV-2026-0009", issuedAt: "2026-09-01", periodStart: "2026-09-01", periodEnd: "2026-09-30", amount: 490, currency: "USD", status: "refunded", url: "#invoice-0009.pdf" },
        { id: "i1", number: "INV-2026-0008", issuedAt: "2026-08-01", periodStart: "2026-08-01", periodEnd: "2026-08-31", amount: 490, currency: "USD", status: "overdue", dueAt: "2026-08-15", url: "#invoice-0008.pdf" },
      ]}
    />
  )
}
```

## <InvoiceHistory>

Props:

- `invoices: Invoice[]`: The invoices, newest first as the app orders them.
- `portal?: { provider: string; href: string } | undefined`: The billing provider's customer portal, where payment methods are changed: its name and the link your server made.
- `state?: ListState | undefined`: What the list is showing: its rows (ready, the default), loading, empty or error with the reason, or partial with a note over the rows. Loading, empty and error draw in place of the rows, in this component's own words, so a list the app doesn't have yet isn't shown as an empty one.
- `className?: string | undefined`

## Strings (`invoiceHistory`)

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

- `label`: Invoices
- `heading`: Invoices
- `empty`: No invoices yet.
- `loading`: Loading invoices
- `error`: Invoices couldn’t be loaded.
- `invoice`: Invoice
- `issued`: Issued
- `period`: Period
- `periodRange`: {start} to {end}
- `amount`: Amount
- `status`: Status
- `download`: Download
- `downloadInvoice`: Download invoice {number}
- `notReady`: Not ready
- `notReadyWhy`: The PDF for {number} isn’t ready yet.
- `paid`: Paid
- `open`: Open
- `overdue`: Overdue
- `void`: Void
- `refunded`: Refunded
- `due`: due {date}
- `portal`: Payment methods and billing are managed at {provider}.
- `openPortal`: Open {provider}

## Types

### Invoice

One invoice as the billing provider issued it.

```ts
{
  id: string;
  /** The invoice's number as printed on it, e.g. "INV-2026-0042". */
  number: string;
  issuedAt: string;
  periodStart: string;
  periodEnd: string;
  /** The total in major units: 49.5 for $49.50. */
  amount: number;
  /** ISO 4217 code, e.g. "USD" or "EUR". */
  currency: string;
  status: "paid" | "open" | "overdue" | "void" | "refunded";
  /** When an open or overdue invoice is due. */
  dueAt?: string | undefined;
  /** Where your app serves the PDF (your route, or the provider's hosted link). Without it the row says it isn't ready. */
  url?: string | undefined;
}
```

## Types from other items

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

### ListState

Import: `import type { ListState } from "@/lib/data"` (item `data`, https://candor.themethodic.com/docs/data.md)

What a list or table is showing, the same shape as `ChartState`: its rows (ready), the shape of rows still coming (loading), nothing and why (empty), the rows it has with a note on what's missing (partial), or why it couldn't load (error). Lists without a `state` show their rows.

```ts
ChartState
```

### ChartState

Import: `import type { ChartState } from "@/lib/data"` (item `data`, https://candor.themethodic.com/docs/data.md)

```ts
{
  kind: "ready";
} | {
  kind: "loading";
}
/** Nothing to plot, and why. */
 | {
  kind: "empty";
  reason: string;
}
/** Some of the range is missing. */
 | {
  kind: "partial";
  note: string;
} | {
  kind: "error";
  reason: string;
}
```

## 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` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
