# Checkout Foundation v18

## Purpose

Checkout converts mutable server-side cart state into an immutable sale snapshot.

The sale is not considered paid merely because checkout succeeded.

v18 intentionally creates:

- sale.status = pending_payment
- sale.payment_status = unpaid

The Payments domain will complete settlement later.

## Idempotency

Clients must send `idempotency_key`.

The key is unique per tenant and stored in `sales.checkout_commands`.

If a mobile/PWA client times out after the server committed the transaction,
retrying the same key returns the same sale instead of creating a duplicate sale.

## Concurrency

Clients also send `expected_version`.

The cart row is locked during checkout.
A stale cart version returns HTTP 409.

## Snapshot

Checkout copies authoritative cart values into:
- sales.sales
- sales.sale_lines

Sale lines contain:
- SKU/name snapshot
- quantity
- unit price
- gross/discount/net
- pricing snapshot
- promotion snapshot

Sale lines are immutable at both application and PostgreSQL trigger level.

## Numbering

Sale numbers are allocated atomically per tenant / branch / business date.

PostgreSQL advisory transaction locking protects the first sequence allocation race.

## Business date

v18 stores both:
- occurred_at as UTC TIMESTAMPTZ
- business_date

Timezone-aware branch/company business-date resolution will be centralized before cash-shift
and fiscal closing flows are introduced.

## APIs

- POST /api/v1/sales/carts/{cartId}/checkout
- GET /api/v1/sales/{saleId}

## Next

Payments Foundation:
- split tender
- cash/card/external methods
- payment attempts
- terminal references
- idempotent settlement
- UNKNOWN state resolution
- sale payment status transition
