Card Authorization, Capture, and Clearing: One Transaction, Three Money States

How to model the card payment as a two-phase auth-then-capture flow plus a clearing tail — handling incremental auths, partial captures, reversals, expiry, and the auth-vs-settled reconciliation that trips up every ledger.

One purchase, three different truths about the money

A card payment feels atomic to the shopper: tap, approved, done. Inside a payment platform it is nothing of the sort. A single purchase moves through at least three distinct money states, and each one answers a different question.

The classic engineering mistake is collapsing these into a single boolean paid. The moment you support anything real — shipping delays, partial fulfilment, tips added after the fact, order edits, expired holds — that boolean lies. The authorized amount and the settled amount routinely differ, and your ledger has to survive the gap between them. The right model is an explicit state machine where each transition is an event you can store, replay, and reconcile.

The state machine, drawn out

Here is the lifecycle a card transaction actually travels. The top rail is the happy path; the branches are the states most first attempts forget to model.

                 incremental auth (raise hold)
                 ┌────────────┐
                 v            │
  [Authorized] ──┴──> [Captured] ──> [Cleared] ──> [Settled]
      │  │                 ^
      │  │  partial capture │ (residual hold released)
      │  └──────────────────┘
      │
      ├──> [Auth Reversed]   (voided before capture)
      │
      └──> [Expired]         (hold aged out, no capture)

Open the interactive diagram — pan, zoom, and trace every step (light/dark, self-contained).

Two properties make this tractable to build. First, transitions are event-sourced: authorized, incremental_auth, captured, reversed, expired, cleared, settled are append-only facts, and the current state is a fold over them. Second, every transition carries an amount and a currency, because the number changes as you go. The authorized amount is a ceiling, not a commitment.

Incremental auths and partial captures

The interesting states live between authorization and capture, and they exist because the final price is often unknown at auth time.

Incremental authorization raises an existing hold. A hotel authorizes an estimate at check-in, then bumps it as the guest orders room service; a fuel pump authorizes a nominal amount, then adjusts to the litres actually dispensed. Each increment references the original authorization and adds to the held total. You are not creating a second transaction — you are amending one, and your model needs a stable authorization identifier that all increments and captures point back to.

Partial capture collects less than what was authorized. Ship two of three items today and you capture two items’ worth; the remaining hold should be released rather than left dangling against the customer’s available balance. Some acquirers auto-release the residual on first capture, others require an explicit reversal for the difference — you cannot assume, you have to encode the behaviour per acquirer.

A minimal state fold makes the invariants obvious:

def fold(events):
    state = {"authorized": 0, "captured": 0, "status": "none"}
    for e in events:
        if e.type == "authorized":
            state["authorized"] = e.amount
            state["status"] = "authorized"
        elif e.type == "incremental_auth":
            state["authorized"] += e.amount
        elif e.type == "captured":
            state["captured"] += e.amount
            state["status"] = "captured"
        elif e.type in ("reversed", "expired"):
            state["status"] = e.type
    # invariant: you may never capture more than you hold
    assert state["captured"] <= state["authorized"]
    return state

That single assertion — captured never exceeds authorized — is the backbone control. If it can ever fail, you have a bug that leaks money or overcharges a cardholder.

Reversals, expiry, and the holds you must release

Authorizations are not free to leave lying around. A hold reduces the cardholder’s available balance whether or not you ever capture it, so unresolved auths generate support tickets and, at scale, real customer harm.

Auth reversal (void) cancels a hold before any capture — the order was cancelled, fraud was flagged, the basket was abandoned at the last step. A reversal should release the full remaining hold and move the transaction to a terminal Auth Reversed state.

Expiry is the silent one. Every authorization has an issuer-defined lifetime — often around seven days, shorter for some card types and merchant categories. If you have not captured by then, the issuer drops the hold regardless of your records. The danger: your system still believes it can capture. Attempting to settle an expired auth gets rejected or, worse, forces a fresh authorization the cardholder never expects. Model expiry as a first-class timed transition, run a sweeper that fires it, and never capture against an auth past its window without re-authorizing.

Clearing, settlement, and reconciling two amounts

Once captured, the transaction enters clearing. The acquirer batches captures and submits them to the scheme; the scheme moves funds and produces settlement records. This is where the authorized amount and the settled amount finally reconcile — and where they are allowed to differ.

They differ legitimately all the time: you authorized 100, captured 80, and 80 settles. You authorized in one currency and settle in another after conversion. A tip pushed the capture above the original auth within the scheme’s tolerance. Your reconciliation job matches on the authorization identifier and classifies each break:

The ledger discipline that keeps this honest: an authorization posts to a memo/hold account, never to revenue. Only capture creates a receivable, and only settlement realizes cash. If your books recognize revenue at auth time, every reversal and every partial capture becomes a painful correcting entry. Separate the accounts and the state machine maps cleanly onto double-entry postings.

What to hold onto

Model the card transaction as an explicit, event-sourced state machine, not a paid flag. Keep a stable authorization identifier that increments and captures reference. Enforce the captured ≤ authorized invariant as a hard control. Treat expiry as a timed transition with a sweeper, and always release residual holds on partial capture and reversal. Finally, reconcile authorized against settled per transaction, allow a tolerance band, and only recognize cash at settlement. Get those five things right and the messy real-world cases — hotels, fuel, tips, split shipments, cancellations — stop being special cases and become ordinary transitions on a state machine you already trust.