Checkout is the point where agent-ready commerce stops being mostly read-only.
Discovery can be a projection. Comparison can be a query. Policy quotation can be a controlled answer backed by source evidence. Checkout is different. Checkout mutates commercial state.
A cart changes. A price may be selected or locked. Inventory may be revalidated or reserved. Shipping options may be calculated. Policy coverage may become a precondition. A checkout snapshot may be created. Payment authority may become relevant. An order may eventually be committed.
That is why checkout should not be modeled as a form endpoint in an agent-ready platform.
It should be modeled as a state machine.
The issue is not user interface style. A human-facing storefront may still present checkout as a familiar sequence of screens: address, shipping, review, payment, confirmation. The issue is the backend boundary. Once agents can add items, prepare checkout, retry operations, act under buyer authority, or approach delegated payment, checkout can no longer be treated as a loose collection of request handlers.
The platform needs to know which state the cart or checkout is in, which command is being requested, which facts were evaluated, which actor was authorized, which transition occurred, which evidence supported it, and what audit record was produced.
This is the sixth article in the Agent-Ready Commerce series.
Part 1 introduced the broader architecture model:
Facts → Eligibility → Authority → State transition → Evidence → Audit
Part 2 focused on commercial truth. It argued that catalog data is not enough. A platform needs source-backed, freshness-aware product facts before agents or other systems can safely rely on product information.
Part 3 focused on action eligibility. It argued that “available” is too broad. A product may be discoverable but not checkout-ready, comparable but not policy-quotable, or checkout-ready for a human flow but not eligible for delegated payment.
Part 4 focused on policy structure. It argued that agents should not interpret free-text policy pages as executable rules. Policies need structured facts with applicability, evidence, lifecycle, conflicts, and quoteability.
Part 5 focused on protocol adapters. It argued that ACP, MCP, AP2, feeds, tools, and future interfaces should translate domain decisions rather than becoming separate sources of commercial meaning.
This article focuses on checkout state.
The central argument is that agent-ready checkout should be treated as a controlled mutation boundary. The platform needs explicit states and commands for cart mutation, validation, checkout preparation, payment authority, retries, expiry, order commitment, evidence, and audit.
Checkout is a mutation boundary
Earlier parts of the series separated several concerns:
Commercial truth says what is known.
Policy facts say which terms apply.
Eligibility says which actions are valid.
Authority says who may request them.
Adapters translate external protocol language.
Checkout is where those concerns start producing durable consequences.
A product being discoverable does not change the product. A comparison result does not reserve inventory. A policy quotation does not create a payment obligation. Checkout can.
Even early checkout operations can matter commercially. Adding an item to a cart may affect totals. Selecting a region may change shipping options. Preparing checkout may create a snapshot. Requesting payment may depend on a mandate. Committing an order may allocate inventory and create obligations for fulfillment, support, accounting, and audit.
That is why checkout needs stronger control than read-only actions.
A useful mental model is:
Read-only actions
↓
Cart mutation
↓
Checkout preparation
↓
Payment authority
↓
Order commitment
Each step increases the cost of being wrong.
If a comparison response is incomplete, the platform can correct the answer. If a checkout state transition is wrong, the system may need to unwind a cart, release inventory, cancel a payment attempt, issue a refund, or explain why an agent acted on a stale commercial state.
The boundary from query to mutation is where the architecture needs to become more explicit.
The Travel Backpack example
Continue with the running example from Parts 2–5:
Product: Travel Backpack
SKU: BAG-TRAVEL-42
Price: €129
Catalog status: active
Inventory status: in_stock
Category: Travel Bags
The commercial truth layer currently says:
Price: fresh
Inventory: stale
Return policy: missing for Travel Bags
Warranty policy: known
Shipping policy: known for EU, unknown for US
Generated description: pending review
Feed publication: last published yesterday
From Part 3, the action matrix was:
| Action | Result |
|---|---|
discover |
allowed |
compare |
allowed |
quote_policy |
blocked |
add_to_cart |
requires_revalidation |
prepare_checkout |
blocked |
delegate_payment |
blocked |
For a human storefront, the Travel Backpack may still appear on a product page. The user can see the title, image, price, and category. Depending on the platform’s risk tolerance, the UI may still allow the user to click “Add to cart” and defer stricter checks until later.
An agent-facing platform needs more precise behavior.
An agent may ask:
Add BAG-TRAVEL-42 to the buyer’s cart.
Prepare checkout for EU delivery.
Use delegated payment if the total remains under €150.
That is not one operation.
It contains at least three different commercial steps:
Cart mutation:
Add the product to a buyer context.
Checkout preparation:
Validate whether the cart can enter checkout.
Delegated payment:
Check whether an actor may proceed under a payment authority boundary.
Each step has different preconditions.
Adding the item may require product identity, current price handling, and inventory revalidation rules.
Preparing checkout may require fresh inventory, policy coverage, shipping context, buyer type, generated-claim constraints, and a stable cart snapshot.
Delegated payment may require checkout validity, actor authority, mandate scope, amount limits, currency match, merchant binding, expiry, revocation checks, and possibly human confirmation.
A single checkout() function cannot express those boundaries safely unless it hides a state machine inside itself. If the state machine exists, it should be modeled explicitly.
A form endpoint hides the decision path
A conventional checkout implementation can easily become endpoint-driven.
A simplified handler might look like this:
async function submitCheckout(req: Request) {
const cart = await carts.get(req.cartId);
await carts.updateShippingAddress(cart.id, req.shippingAddress);
await carts.recalculateTotals(cart.id);
const paymentSession = await payments.createSession(cart);
const order = await orders.place(cart, paymentSession);
return order;
}
This example is intentionally simplified, but it shows the boundary problem. Several commercial transitions are compressed into one request handler.
The handler may work for a basic human checkout form. It is weak as an agent-facing mutation boundary because the system cannot easily answer:
Which cart snapshot was evaluated?
Was inventory fresh at the time checkout was prepared?
Which policy facts were required?
Was return-policy coverage missing?
Was checkout blocked before payment?
Did the actor have authority to prepare checkout?
Did the actor have authority to request payment?
Did the cart change after authority was granted?
Was this a retry of an earlier command?
Did a previous attempt create a payment session before the response failed?
Which evidence supported the transition?
These questions matter because agents and protocol adapters may call checkout operations directly. They may retry. They may operate asynchronously. They may ask for checkout readiness before a human would normally reach the payment screen. They may carry payment authority artifacts from outside the platform.
A checkout form can hide state from the user.
The backend should not hide state from itself.
State is not just a status field
A common implementation compromise is to add a status field:
type CheckoutStatus = "active" | "completed" | "failed";
That is not enough.
A checkout can be active but not valid. It can be valid but not prepared. It can be prepared but waiting for payment authority. It can have authority but fail snapshot matching. It can be blocked because policy coverage is missing. It can be expired. It can be cancelled. It can be awaiting revalidation.
Those are materially different states.
A more useful model names the commercial boundaries:
type CheckoutState =
| "draft_cart"
| "cart_requires_revalidation"
| "cart_blocked"
| "checkout_ready"
| "checkout_prepared"
| "payment_authority_required"
| "payment_authority_validated"
| "payment_pending"
| "order_committed"
| "expired"
| "cancelled"
| "failed";
The exact names are less important than the distinction they create.
A draft_cart can accept item changes.
A cart_requires_revalidation tells the platform that some fact or mutation invalidated prior readiness.
A cart_blocked means the system has blockers that prevent checkout preparation.
A checkout_prepared state means the platform created a stable checkout context.
A payment_authority_required state means checkout may be commercially valid, but actor authority is not yet sufficient for payment.
An order_committed state means the transition crossed into order creation.
These states do not describe screens. They describe what the platform believes it can safely do next.
Commands should drive transitions
A checkout state machine should not be mutated by arbitrary updates. It should accept commands.
Commands represent requested state changes:
type CheckoutCommand =
| "add_item"
| "remove_item"
| "revalidate_cart"
| "select_shipping_region"
| "select_shipping_method"
| "prepare_checkout"
| "attach_payment_authority"
| "request_payment"
| "commit_order"
| "expire_checkout"
| "cancel_checkout";
Each command has preconditions.
| Command | Example preconditions |
|---|---|
add_item |
Product identity known, actor may mutate cart, cart is mutable |
revalidate_cart |
Cart exists, relevant facts can be refreshed or checked |
prepare_checkout |
Price, inventory, policy coverage, shipping, buyer context, generated-claim constraints, and cart snapshot are valid |
attach_payment_authority |
Checkout exists, actor context is known, authority artifact is present |
request_payment |
Checkout prepared, payment authority valid, cart snapshot matches, amount and currency are in scope |
commit_order |
Payment result satisfies order rules, cart snapshot is still valid, order creation is idempotent |
A command result should report the transition outcome:
type CheckoutCommandRequest = {
command: CheckoutCommand;
checkoutId: string;
actorId: string;
idempotencyKey: string;
context: {
region?: string;
buyerType?: "consumer" | "business";
channel: "storefront" | "agent" | "marketplace";
currency?: string;
};
payload: unknown;
};
type CheckoutCommandResult = {
accepted: boolean;
previousState: CheckoutState;
nextState: CheckoutState;
blockers: Array<{
code: string;
message: string;
nextAction?: string;
}>;
evidenceRefs: string[];
auditEventId: string;
};
This is different from returning only an HTTP success or failure.
The HTTP request may succeed because the platform processed the command. The checkout command may still be rejected because the transition was not valid.
That distinction is important for agents. A blocked transition is not an infrastructure error. It is a domain answer.
Eligibility, authority, and transition are separate checks
A checkout transition should not collapse eligibility, authority, and state.
They answer different questions.
Eligibility asks:
Is this action valid for this product, cart, buyer, region, channel, and current facts?
Authority asks:
Is this actor allowed to request this action?
State transition asks:
Given the current checkout state, can this command move the checkout to a new state?
All three can fail independently.
A product may be eligible for checkout, but the checkout session may be expired.
An actor may have authority to request checkout preparation, but the cart may be blocked because policy coverage is incomplete.
A checkout may be prepared, but delegated payment authority may be missing.
A payment authority may be present, but the cart snapshot may no longer match.
A useful execution path is:
Command received
↓
Current checkout state loaded
↓
Commercial facts selected
↓
Eligibility evaluated
↓
Authority evaluated
↓
Transition preconditions evaluated
↓
State transition applied or blocked
↓
Evidence and audit recorded
This is where the Part 1 model becomes operational.
Facts → Eligibility → Authority → State transition → Evidence → Audit
Checkout is the first article in the series where the full chain becomes unavoidable.
Cart mutation is not harmless
Cart mutation is sometimes treated as low risk because the buyer has not paid yet.
That assumption is weak in agent-facing commerce.
A cart can become the basis for checkout preparation, payment authority, price display, policy quotation, tax calculation, promotion eligibility, or audit records. Once a cart is used as input to delegated payment, it is no longer just a temporary UI convenience.
The platform needs to distinguish mutable carts from validated snapshots.
A simple cart state model might be:
type CartState =
| "mutable"
| "requires_revalidation"
| "validated"
| "locked_for_checkout"
| "expired";
In mutable, items can be added or removed.
In requires_revalidation, a cart change or stale fact means prior readiness can no longer be trusted.
In validated, the platform has checked the relevant facts for the current context.
In locked_for_checkout, the cart snapshot is being used for checkout preparation or payment authority.
In expired, the cart can no longer support payment or order commitment without revalidation.
For the Travel Backpack:
add_item:
requires revalidation because inventory is stale
prepare_checkout:
blocked because inventory is stale and return-policy coverage is missing
The platform should not silently move from item addition to checkout readiness. The transition should be explicit.
Snapshots are the contract boundary
Checkout needs snapshots because mutable carts are not stable enough for payment authority or order commitment.
A cart ID identifies a container. It does not prove that the cart contents, price, currency, shipping region, taxes, policies, or total remain the same.
For agent-ready commerce, this matters because delegated payment usually depends on the buyer authorizing a bounded action.
For example:
Buy one Travel Backpack for up to €150 in EUR from this merchant.
That authority should apply to a specific commercial snapshot, not to any future contents of the same cart ID.
A cart snapshot might include:
type CartSnapshot = {
snapshotId: string;
cartId: string;
items: Array<{
sku: string;
quantity: number;
priceAmount: number;
currency: string;
}>;
context: {
region?: string;
buyerType?: "consumer" | "business";
channel: "storefront" | "agent" | "marketplace";
};
totals: {
subtotal: number;
shipping?: number;
tax?: number;
total: number;
currency: string;
};
factRefs: string[];
policyFactRefs: string[];
createdAt: string;
};
A payment authority should reference the snapshot it applies to:
type PaymentAuthorityRef = {
authorityId: string;
cartSnapshotId: string;
maxAmount: number;
currency: string;
merchantId: string;
expiresAt: string;
};
Before payment, the platform should check:
Does the current cart snapshot match the authorized snapshot?
Is the amount within scope?
Is the currency unchanged?
Is the merchant unchanged?
Has the authority expired?
Has the authority been revoked?
This prevents a serious failure mode: the buyer authorizes one commercial state and the platform charges for another.
A mutable cart is not a mandate. A snapshot is closer to the boundary the platform can reason about.
Checkout preparation should be an explicit transition
prepare_checkout should not mean “create a checkout URL if possible.”
It should mean:
Evaluate whether this cart can enter a checkout-prepared state for this buyer context.
That transition should require:
Product identity
Price freshness
Inventory freshness
Policy coverage
Shipping region
Buyer type
Channel
Generated-claim constraints
Tax and total calculation readiness
Cart snapshot integrity
Actor authority for preparation
For the Travel Backpack, prepare_checkout should be blocked:
Reasons:
- inventory is stale
- return-policy coverage is missing for Travel Bags
A structured result might look like this:
const prepareCheckoutResult = {
command: "prepare_checkout",
previousState: "cart_requires_revalidation",
nextState: "cart_blocked",
accepted: false,
blockers: [
{
code: "INVENTORY_STALE",
message: "Inventory must be revalidated before checkout can be prepared.",
nextAction: "Revalidate inventory from the inventory source."
},
{
code: "RETURN_POLICY_MISSING",
message:
"Return-policy coverage is missing for the Travel Bags category.",
nextAction:
"Attach or approve return-policy coverage for Travel Bags."
}
],
evidenceRefs: [
"truth:BAG-TRAVEL-42:inventory",
"policy:travel_bags:returns"
]
};
That response gives the agent a clear boundary. It gives operators actionable remediation. It gives audit a reason.
A boolean such as checkoutReady: false does not do that.
Payment should not be the first hard validation point
Some checkout systems perform their strictest validation late, near payment or order placement.
That is too late for agent-facing commerce.
Final validation still matters. The platform should always recheck critical facts before payment or order commitment. But obvious blockers should be detected earlier.
If return-policy coverage is missing, the platform should not wait until payment to fail.
If inventory is stale, the platform should not ask for delegated payment first and then discover the cart cannot be prepared.
If US shipping policy is unknown, the platform should not let the agent proceed through a US checkout path and fail at the end with a generic payment or fulfillment error.
Earlier transitions should fail earlier.
For the Travel Backpack, the explainable sequence is:
quote_policy:
blocked for returns
prepare_checkout:
blocked because inventory is stale and return-policy coverage is missing
delegate_payment:
blocked because checkout is not valid
A late failure such as:
payment_failed
does not explain the real domain issue.
A better checkout model preserves the real blocker:
checkout_not_valid:
inventory stale
return-policy coverage missing
Agents need that distinction because the next action differs. A payment failure may suggest retrying payment. A policy blocker suggests operator remediation. An inventory blocker suggests revalidation.
Payment authority is a dependency, not a shortcut
Delegated payment depends on checkout state, but it should not be collapsed into checkout state.
A checkout can be prepared and still lack payment authority.
A payment authority can be present and still not match the cart snapshot.
A payment authority can be valid for one amount and invalid for another.
A payment authority can expire or be revoked before use.
A simplified payment-related state path might look like this:
checkout_prepared
↓
payment_authority_required
↓
payment_authority_validated
↓
payment_pending
↓
order_committed
For delegated payment, the platform should verify:
checkout session is valid
cart snapshot matches
amount is within the mandate
currency matches
merchant matches
authority has not expired
authority has not been revoked
actor is allowed to use the authority
required confirmation has been satisfied
This should happen before payment is requested.
The adapter may carry AP2-related or other payment-protocol artifacts. Those artifacts are inputs. They are not permission by themselves.
This continues the boundary from Part 5:
Protocol validity is not payment authority.
Payment authority is not checkout validity.
Checkout validity is not order commitment.
A state machine makes those boundaries visible.
Expiry is part of correctness
Checkout state should expire.
Prices can change. Inventory can change. Shipping availability can change. Policy coverage can change. Payment mandates can expire. Generated claims may be revoked. A cart snapshot may become too old to support payment or order commitment.
Expiry should be part of the checkout model, not a hidden timestamp checked inconsistently across handlers.
type CheckoutSession = {
checkoutId: string;
state: CheckoutState;
cartSnapshotId: string;
preparedAt?: string;
expiresAt?: string;
blockers: string[];
};
If a session expires, later commands should not treat it as current.
For example:
request_payment on expired checkout:
blocked
Reason:
Checkout session expired. Revalidation is required before payment can be requested.
This is especially important for agent workflows because interactions may be asynchronous. An agent may prepare checkout, wait for buyer confirmation, then return after the snapshot is no longer valid.
The platform should not rely on the agent to remember expiry. It should enforce expiry at the state boundary.
Retries are part of the design
Agent-facing systems must assume retries.
A tool call may be repeated. A network response may be lost. A checkout preparation request may time out after the checkout session is created. A payment attempt may return an ambiguous result. An agent may call the same operation again after receiving no response.
Retry behavior should be designed, not discovered in production.
Each state-changing command should carry an idempotency key:
type CheckoutCommandEnvelope = {
commandId: string;
idempotencyKey: string;
command: CheckoutCommand;
checkoutId: string;
requestedAt: string;
};
If the same command is retried, the platform should return the same result when appropriate instead of creating a duplicate transition.
Example:
First request:
prepare_checkout creates checkout session chk_123.
Network response:
lost.
Retry with same idempotency key:
returns checkout session chk_123, not a second checkout session.
Idempotency belongs where the state transition occurs.
A protocol adapter can pass the key. The checkout service must enforce it.
If idempotency exists only in the adapter, duplicate transitions can still happen through another adapter, feed-triggered workflow, admin action, or retry path.
Concurrency cannot be ignored
Checkout is also vulnerable to concurrent changes.
A buyer may modify the cart while an agent prepares checkout. Inventory may be revalidated while a payment authority is being attached. A promotion may expire while shipping is recalculated. An operator may update a policy while an agent is quoting terms.
A state machine does not eliminate concurrency problems, but it gives the platform a place to handle them.
Useful techniques include:
Cart snapshot identifiers
Optimistic concurrency checks
State transition guards
Idempotency keys
Expiry timestamps
Final validation before payment or order commitment
For example, request_payment should fail if the cart snapshot used by the payment authority is not the current checkout snapshot.
request_payment:
blocked
Reason:
Cart snapshot changed after payment authority was granted.
This is not a generic technical conflict. It is a commercial safety rule.
The buyer authorized one thing. The cart now represents something else. The platform must not proceed silently.
State transitions need evidence
A checkout transition should record the evidence used to allow or block it.
For an allowed transition, evidence explains what the platform relied on.
For a blocked transition, evidence explains why the platform refused.
A checkout_prepared transition may reference:
price fact
inventory validation
policy coverage facts
shipping calculation
tax calculation
cart snapshot
eligibility decision
authority decision
A blocked prepare_checkout transition for the Travel Backpack may reference:
inventory stale fact
missing return-policy coverage
current cart snapshot
buyer context
eligibility decision
A transition record might look like this:
type CheckoutTransitionRecord = {
transitionId: string;
checkoutId: string;
command: CheckoutCommand;
previousState: CheckoutState;
nextState: CheckoutState;
accepted: boolean;
actorId: string;
idempotencyKey: string;
evidenceRefs: string[];
blockers: string[];
occurredAt: string;
};
This record is useful for more than compliance. It supports agent responses, operator remediation, debugging, replay analysis, and customer support.
When checkout is blocked, the platform should not have to reconstruct the reason from logs. The transition should already contain the reason.
Audit should record the decision path
An audit log that says “checkout failed” is not enough.
For agent-ready checkout, audit should capture the decision path:
Who or what requested the command
Which protocol or channel was used
Which cart snapshot was evaluated
Which product facts were used
Which policy facts were used
Which eligibility decision was produced
Which authority decision was produced
Which state transition was attempted
Whether the transition was accepted or blocked
Which blockers were returned
Which idempotency key was used
Which evidence records were attached
This allows the platform to answer questions such as:
Why did the agent say checkout could not proceed?
Was inventory stale?
Was a return policy missing?
Did the buyer authorize payment?
Did the cart change after authorization?
Did a retry create a duplicate operation?
Which adapter initiated the request?
Which transition produced the final state?
Audit is not a logging afterthought. It is part of the safety model.
The Part 1 chain ends with audit because agent-facing actions need explainable history.
Blocked transitions should generate operator work
A blocked checkout transition should create or update operator tasks when the blocker is actionable.
For the Travel Backpack:
prepare_checkout:
blocked
Blockers:
- INVENTORY_STALE
- RETURN_POLICY_MISSING
The platform can produce tasks:
Task 1:
Revalidate inventory for BAG-TRAVEL-42.
Impact:
Agents can discover and compare the product, but checkout preparation is blocked.
Task 2:
Attach or approve return-policy coverage for Travel Bags.
Impact:
Agents cannot quote return terms and checkout preparation is blocked if complete policy coverage is required.
This is better than leaving errors inside checkout logs.
Operator tasks connect runtime blockers to remediation. That matters because agent-readiness is partly operational. Products become blocked, stale, partially ready, or checkout-ready based on fixable commercial data.
The checkout state machine becomes one source of evidence for that operational layer.
Protocol adapters should not perform hidden transitions
Part 5 argued that adapters should translate domain decisions rather than own them. Checkout is where that boundary becomes critical.
A protocol adapter should not create a checkout session directly because an external request asked for one.
Bad boundary:
async function adapterHandler(req: ProtocolRequest) {
const cart = await carts.get(req.cartId);
const session = await payments.createCheckoutSession(cart);
await carts.markCheckoutStarted(cart.id);
return project(session);
}
This lets the adapter perform hidden transitions.
Better boundary:
async function adapterHandler(req: ProtocolRequest) {
const command = mapToCheckoutCommand(req);
const result = await checkout.handleCommand(command);
return projectCheckoutResult(result);
}
The adapter still has important work. It validates protocol shape, normalizes context, passes idempotency metadata, and projects the response.
But the checkout domain owns the mutation.
That prevents ACP, MCP, AP2, internal feeds, and future adapters from creating different checkout paths.
The state machine should coordinate, not replace domains
A checkout state machine can also be overbuilt.
It should not become the place where every commerce rule is implemented directly.
A useful boundary is:
Commercial truth service:
Provides source-backed product facts.
Policy service:
Provides applicable policy facts and quoteability.
Eligibility service:
Decides whether actions are valid.
Authority service:
Decides whether the actor may request the action.
Checkout state machine:
Controls checkout states and transitions.
Payment service:
Handles payment-specific operations.
Audit service:
Records decisions and transitions.
The checkout state machine coordinates these decisions at the mutation boundary. It should not duplicate all of them internally.
If checkout reimplements eligibility differently from the feed, policy quotation, or admin readiness views, the platform reintroduces the semantic drift discussed in Part 5.
The state machine should consume shared domain decisions and then decide whether a transition can occur from the current state.
State names should reflect business meaning
A checkout state machine should use state names that describe commercial meaning, not UI steps or handler names.
Weak state names:
step_1
step_2
payment_screen
submit_clicked
handler_complete
These describe implementation flow. They are not useful for agents, operators, or audit.
Better state names:
draft_cart
cart_requires_revalidation
cart_blocked
checkout_ready
checkout_prepared
payment_authority_required
payment_pending
order_committed
expired
cancelled
failed
These names communicate what the platform believes about the checkout.
A useful test is whether an operator or support engineer can understand the state without reading the UI code.
If the state name only makes sense as a page in a checkout form, it is probably not the right state for agent-ready commerce.
Not every validation deserves a persistent state
There is also a risk of over-modeling.
A platform should not create a persistent state for every internal check.
For example:
checking_price
checking_inventory
checking_policy
checking_tax
checking_shipping
checking_authority
These may be internal steps inside a transition, not durable states.
A practical rule:
Make it a state when different commands are allowed, blocked, required, retried, expired, or audited differently.
Keep it as transition logic when it is only an internal validation step.
For example, cart_requires_revalidation is useful because it changes what can happen next. checking_policy may not be useful unless the system genuinely waits for asynchronous policy review.
Good state-machine design is not about maximizing the number of states. It is about naming the boundaries that affect behavior.
Where this model can go wrong
Explicit checkout state improves safety, but it introduces its own failure modes.
The state machine mirrors the UI
If states are based on screens, the model becomes fragile. Agent-facing checkout may not use the same screens as a human storefront. Protocol flows may skip visual steps entirely.
State should represent commercial readiness, not UI layout.
Too few states
A model with only active, submitted, and complete hides important differences.
A cart requiring revalidation is not the same as a checkout session waiting for payment authority. A blocked checkout is not the same as an expired checkout. A prepared checkout is not the same as a committed order.
Too few states recreate the original problem.
Too many states
A state machine with too many states becomes difficult to reason about. Engineers may not know which transitions are valid. Operators may not understand what the state means.
Persistent states should represent meaningful business boundaries, not every validation sub-step.
Hidden transitions inside adapters
If protocol adapters mutate checkout state directly, the state machine is bypassed. Different adapters may create different checkout paths.
All state changes should go through the checkout command boundary.
Eligibility is duplicated inside checkout
Checkout should consume eligibility decisions, not reimplement separate rules that contradict feeds, policy quotation, or admin surfaces.
Some final validation belongs in checkout, but the decision model should remain consistent.
Authority is treated as checkout state
Payment authority should not be reduced to checkout.authorized = true.
Authority has scope, expiry, revocation, amount limits, currency, merchant binding, actor identity, and confirmation requirements. Checkout can reference authority decisions. It should not oversimplify them.
Cart snapshots are not locked
If payment authority references a mutable cart, the buyer may authorize one thing and the platform may charge for another.
Snapshot identity should be explicit.
Retry behavior is unclear
If retries are not idempotent, duplicate checkout sessions, payment attempts, or order commits can occur.
Idempotency should be enforced at the state-transition boundary.
Expiry is implicit
If checkout expiry is hidden in timestamps or external payment sessions, the platform may accept stale transitions.
Expiry should be part of checkout state behavior.
Audit records only success
Blocked transitions are as important as successful transitions. They explain why agents could not proceed and what operators need to fix.
Audit should record attempted commands, decisions, blockers, evidence, and state transitions.
Testing checkout state
Checkout state should be tested with scenario matrices, not only endpoint tests.
A useful test includes:
Initial state
Command
Commercial facts
Policy coverage
Buyer context
Authority context
Expected next state
Expected blockers
Expected audit event
Expected idempotency behavior
For the Travel Backpack:
| Scenario | Expected result |
|---|---|
| Add Travel Backpack to draft cart with stale inventory | Cart enters cart_requires_revalidation
|
| Prepare checkout with stale inventory and missing return policy | Transition blocked; state becomes or remains cart_blocked
|
| Revalidate inventory but return policy remains missing | Checkout remains blocked for policy coverage |
| Add return-policy coverage but US shipping remains unknown for US buyer | US checkout blocked or requires review |
| EU buyer, fresh inventory, complete policy coverage | Checkout can move to checkout_prepared
|
| Delegated payment requested without authority | State moves to or remains payment_authority_required
|
| Delegated payment authority present but cart snapshot changed | Payment request blocked |
Retry prepare_checkout with same idempotency key |
Same result returned; no duplicate checkout session |
Retry commit_order after ambiguous response |
Existing order result returned if already committed |
| Checkout session expired before payment | Payment request blocked; revalidation required |
A simplified expectation type:
type CheckoutScenarioExpectation = {
initialState: CheckoutState;
command: CheckoutCommand;
expectedAccepted: boolean;
expectedNextState: CheckoutState;
expectedBlockerCodes: string[];
expectedAuditEvent: boolean;
};
The value of these tests is not only correctness. It is explainability.
When a scenario fails, the team can see which boundary was violated: facts, eligibility, authority, transition, evidence, or audit.
A practical implementation path
A platform does not need a large workflow engine to improve checkout safety.
A practical path is:
Name the mutation boundaries.
Start withdraft_cart,cart_requires_revalidation,cart_blocked,checkout_ready,checkout_prepared,payment_authority_required,payment_pending,order_committed,expired, andcancelled.Move mutations behind commands.
Use commands such asadd_item,revalidate_cart,prepare_checkout,attach_payment_authority,request_payment,commit_order, andcancel_checkout.Define transition preconditions.
For each command, define required current state, eligibility checks, authority checks, and required facts.Introduce cart snapshots.
Payment authority and checkout preparation should reference stable cart snapshots, not only mutable cart IDs.Enforce idempotency at the transition boundary.
Adapters may pass idempotency keys, but the checkout service should enforce safe retry behavior.Record transition evidence.
Allowed and blocked transitions should record facts, policy coverage, eligibility decisions, authority decisions, blockers, and audit events.Keep adapters outside mutation logic.
Adapters should map protocol requests to checkout commands and project results. They should not perform hidden transitions.Generate operator tasks from blockers.
Missing policy, stale inventory, unsupported region, expired authority, and generated-claim review should produce actionable remediation when appropriate.Test scenario matrices.
Test state, command, facts, authority, expected transition, blockers, idempotency, and audit together.
This path moves checkout away from loosely coupled request handlers and toward a domain model that can support agent-facing actions.
The tradeoff
A checkout state machine adds structure.
It requires named states, commands, transition rules, idempotency handling, snapshots, evidence records, audit events, and scenario tests.
For a simple storefront with low-risk products and no agent-facing checkout or delegated payment, this may be unnecessary. A conventional checkout flow may be sufficient.
Agent-ready commerce changes the threshold.
Once agents can prepare checkout, operate across protocols, retry tool calls, act under buyer authority, or approach payment delegation, checkout becomes a mutation boundary that must be explicit.
The tradeoff is between implementation convenience and mutation safety.
Request handlers are faster to write.
State transitions are easier to reason about, test, retry, audit, and integrate across protocols.
The goal is not to make checkout complicated. The goal is to stop hiding commercial mutations inside endpoints that cannot explain what happened.
The practical takeaway
Checkout is not only a form.
For agent-ready commerce, checkout is where commercial truth, policy coverage, action eligibility, actor authority, cart mutation, payment boundaries, evidence, and audit converge.
That convergence needs explicit state.
The Travel Backpack example shows why.
The product is active and in stock at the catalog level. It can be discovered. It can be compared. But inventory is stale, return-policy coverage is missing for Travel Bags, US shipping is unknown, and the generated description is pending review. The platform should not prepare checkout or allow delegated payment just because a protocol request asks for it.
A state-machine model makes the boundary explicit:
Cart mutation requires validation.
Checkout preparation requires fresh facts and policy coverage.
Delegated payment requires valid checkout and authority.
Retries require idempotency.
Payment requires a stable cart snapshot.
Blocked transitions require evidence.
Audit records the decision path.
This keeps checkout aligned with the broader architecture model:
Facts → Eligibility → Authority → State transition → Evidence → Audit
Part 7 will move from checkout state to payment authority:
Agent-Ready Commerce, Part 7: Delegated Payment Needs More Than a Token
That article will examine why payment delegation requires mandate scope, cart snapshot integrity, amount limits, merchant binding, expiry, revocation, confirmation rules, evidence, and audit rather than treating a payment artifact as permission to charge.
About the author
Written by Dimitrios S. Sfyris, Founder & Software Architect at AspectSoft.
AspectSoft designs and develops custom software platforms, e-commerce systems, SaaS infrastructure, integrations, analytics tools, and practical digital products.
You can also follow the AspectSoft LinkedIn page for updates on software platforms, commerce systems, AI tooling, and developer-focused products.
Top comments (0)