Example · checkout flow

Validate before you charge

Why a checkout flow checks its own data before calling the external payment service

Confirm the item is in stock (1), then charge the card (2), then confirm the order lines were built (3). Splitting it this way stops you from charging a customer when the order can't actually be fulfilled.

· Space · swipe to navigate

01 · Overview

Think of a shop counter

1

Check the shelf

Is the item actually in stock? Decided at the product level — no customer involved yet. If not, stop here.

2

Ring it up

Call the payment service and charge the card. This is the moment the charge record is created.

3

Print the receipt

Build the order lines for this order. If they come out empty, stop; otherwise confirm.

02 · Flow

End to end — red stops, green proceeds

flowchart LR Start(["Customer: checkout"]) --> G1{"1. Pre-check
in stock?"} G1 -- "no" --> B1["Stop · 409
out of stock"]:::stop G1 -- "yes" --> Pay["2. Charge
payment service"]:::act Pay --> EQ{"charge ok?"} EQ -- "no" --> B2["Stop · 402
payment failed"]:::stop EQ -- "yes" --> G3{"3. Post-check
order lines built?"} G3 -- "no" --> B3["Stop · 500
no order data"]:::stop G3 -- "yes" --> Done(["Proceed · confirm order"]):::ok classDef stop fill:#371a1a,stroke:#f87171,color:#fecaca classDef act fill:#382a0f,stroke:#fbbf24,color:#fde68a classDef ok fill:#103027,stroke:#34d399,color:#a7f3d0
Three gates (pre-check, charge, post-check). If any one fails the request stops.
03 · Sequence

The charge record is created in step 2

sequenceDiagram autonumber participant C as Client participant S as Order API participant P as Payment service C->>S: place order S->>S: pre-check stock (no charge record yet) S->>P: charge card P-->>S: charge record created S->>S: post-check order lines for this order S-->>C: order confirmed
Before step 3 (the charge call) the order has no charge record. That is why step 1 cannot look at it.
04 · Key point

Why doesn't the pre-check look at the charge record?

WHY

The charge record only exists after step 2. If the pre-check required it, every first-time checkout would see nothing and be blocked — even when the item is in stock. So the pre-check looks only at product-level stock, never at the per-order charge.

05 · Before / After

What this ordering prevents

WHY IT MATTERS

When the data is not ready, the validate-first order never calls the payment service, so there is no charge to refund. The real win is not "more checks" — it is not doing the irreversible step on bad input.

06 · Scenarios

How the two checks line up

Illustrative — pre-check (stock) vs post-check (this order's lines)
SituationPre-check (stock)Post-check (this order)Result
In stock + already chargedpasslines builtproceed
In stock + not charged yet (before step 2)passnoneexpected — charge not made yet
Out of stockblockno payment call
FACTS
  • The pre-check passes on stock alone, independent of whether this customer has been charged.
  • A "not charged yet" order reads as none on the post-check — which is exactly why the pre-check must not depend on it.
07 · Numbers (illustrative)

What the change buys you

3
gates — pre-check, charge, post-check
0
orphan charges when stock is missing
1
wasted external call avoided per bad request
08 · Summary

Key takeaways

  • Two gates around the irreversible step — pre-check (product) then charge then post-check (this order).
  • The pre-check ignores per-order data — that data is created by the charge, so checking it first would block everyone.
  • Fail before side effects — no stock means no payment call, so nothing to refund.

Generic illustrative example · built with mermaid-slide-deck (TEMPLATE.html). Numbers are made up to show the layout.