{"slug": "agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form", "title": "Agent-Ready Commerce, Part 6: Checkout Is a State Machine, Not a Form", "summary": "A developer argues that checkout in agent-ready commerce should be modeled as a state machine rather than a form endpoint. The platform must track explicit states and commands for cart mutation, validation, checkout preparation, payment authority, retries, expiry, order commitment, evidence, and audit. This approach ensures controlled mutation boundaries as checkout operations produce durable commercial consequences.", "body_md": "Checkout is the point where agent-ready commerce stops being mostly read-only.\n\nDiscovery 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.\n\nA 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.\n\nThat is why checkout should not be modeled as a form endpoint in an agent-ready platform.\n\nIt should be modeled as a state machine.\n\nThe 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.\n\nThe 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.\n\nThis is the sixth article in the Agent-Ready Commerce series.\n\nPart 1 introduced the broader architecture model:\n\n```\nFacts → Eligibility → Authority → State transition → Evidence → Audit\n```\n\nPart 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.\n\nPart 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.\n\nPart 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.\n\nPart 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.\n\nThis article focuses on checkout state.\n\nThe 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.\n\nEarlier parts of the series separated several concerns:\n\n```\nCommercial truth says what is known.\nPolicy facts say which terms apply.\nEligibility says which actions are valid.\nAuthority says who may request them.\nAdapters translate external protocol language.\n```\n\nCheckout is where those concerns start producing durable consequences.\n\nA 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.\n\nEven 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.\n\nThat is why checkout needs stronger control than read-only actions.\n\nA useful mental model is:\n\n```\nRead-only actions\n      ↓\nCart mutation\n      ↓\nCheckout preparation\n      ↓\nPayment authority\n      ↓\nOrder commitment\n```\n\nEach step increases the cost of being wrong.\n\nIf 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.\n\nThe boundary from query to mutation is where the architecture needs to become more explicit.\n\nContinue with the running example from Parts 2–5:\n\n```\nProduct: Travel Backpack\nSKU: BAG-TRAVEL-42\nPrice: €129\nCatalog status: active\nInventory status: in_stock\nCategory: Travel Bags\n```\n\nThe commercial truth layer currently says:\n\n```\nPrice: fresh\nInventory: stale\nReturn policy: missing for Travel Bags\nWarranty policy: known\nShipping policy: known for EU, unknown for US\nGenerated description: pending review\nFeed publication: last published yesterday\n```\n\nFrom Part 3, the action matrix was:\n\n| Action | Result |\n|---|---|\n`discover` |\nallowed |\n`compare` |\nallowed |\n`quote_policy` |\nblocked |\n`add_to_cart` |\nrequires_revalidation |\n`prepare_checkout` |\nblocked |\n`delegate_payment` |\nblocked |\n\nFor 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.\n\nAn agent-facing platform needs more precise behavior.\n\nAn agent may ask:\n\n```\nAdd BAG-TRAVEL-42 to the buyer’s cart.\nPrepare checkout for EU delivery.\nUse delegated payment if the total remains under €150.\n```\n\nThat is not one operation.\n\nIt contains at least three different commercial steps:\n\n```\nCart mutation:\nAdd the product to a buyer context.\n\nCheckout preparation:\nValidate whether the cart can enter checkout.\n\nDelegated payment:\nCheck whether an actor may proceed under a payment authority boundary.\n```\n\nEach step has different preconditions.\n\nAdding the item may require product identity, current price handling, and inventory revalidation rules.\n\nPreparing checkout may require fresh inventory, policy coverage, shipping context, buyer type, generated-claim constraints, and a stable cart snapshot.\n\nDelegated payment may require checkout validity, actor authority, mandate scope, amount limits, currency match, merchant binding, expiry, revocation checks, and possibly human confirmation.\n\nA single `checkout()`\n\nfunction cannot express those boundaries safely unless it hides a state machine inside itself. If the state machine exists, it should be modeled explicitly.\n\nA conventional checkout implementation can easily become endpoint-driven.\n\nA simplified handler might look like this:\n\n``` js\nasync function submitCheckout(req: Request) {\n  const cart = await carts.get(req.cartId);\n\n  await carts.updateShippingAddress(cart.id, req.shippingAddress);\n  await carts.recalculateTotals(cart.id);\n\n  const paymentSession = await payments.createSession(cart);\n  const order = await orders.place(cart, paymentSession);\n\n  return order;\n}\n```\n\nThis example is intentionally simplified, but it shows the boundary problem. Several commercial transitions are compressed into one request handler.\n\nThe handler may work for a basic human checkout form. It is weak as an agent-facing mutation boundary because the system cannot easily answer:\n\n```\nWhich cart snapshot was evaluated?\nWas inventory fresh at the time checkout was prepared?\nWhich policy facts were required?\nWas return-policy coverage missing?\nWas checkout blocked before payment?\nDid the actor have authority to prepare checkout?\nDid the actor have authority to request payment?\nDid the cart change after authority was granted?\nWas this a retry of an earlier command?\nDid a previous attempt create a payment session before the response failed?\nWhich evidence supported the transition?\n```\n\nThese 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.\n\nA checkout form can hide state from the user.\n\nThe backend should not hide state from itself.\n\nA common implementation compromise is to add a status field:\n\n```\ntype CheckoutStatus = \"active\" | \"completed\" | \"failed\";\n```\n\nThat is not enough.\n\nA 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.\n\nThose are materially different states.\n\nA more useful model names the commercial boundaries:\n\n```\ntype CheckoutState =\n  | \"draft_cart\"\n  | \"cart_requires_revalidation\"\n  | \"cart_blocked\"\n  | \"checkout_ready\"\n  | \"checkout_prepared\"\n  | \"payment_authority_required\"\n  | \"payment_authority_validated\"\n  | \"payment_pending\"\n  | \"order_committed\"\n  | \"expired\"\n  | \"cancelled\"\n  | \"failed\";\n```\n\nThe exact names are less important than the distinction they create.\n\nA `draft_cart`\n\ncan accept item changes.\n\nA `cart_requires_revalidation`\n\ntells the platform that some fact or mutation invalidated prior readiness.\n\nA `cart_blocked`\n\nmeans the system has blockers that prevent checkout preparation.\n\nA `checkout_prepared`\n\nstate means the platform created a stable checkout context.\n\nA `payment_authority_required`\n\nstate means checkout may be commercially valid, but actor authority is not yet sufficient for payment.\n\nAn `order_committed`\n\nstate means the transition crossed into order creation.\n\nThese states do not describe screens. They describe what the platform believes it can safely do next.\n\nA checkout state machine should not be mutated by arbitrary updates. It should accept commands.\n\nCommands represent requested state changes:\n\n```\ntype CheckoutCommand =\n  | \"add_item\"\n  | \"remove_item\"\n  | \"revalidate_cart\"\n  | \"select_shipping_region\"\n  | \"select_shipping_method\"\n  | \"prepare_checkout\"\n  | \"attach_payment_authority\"\n  | \"request_payment\"\n  | \"commit_order\"\n  | \"expire_checkout\"\n  | \"cancel_checkout\";\n```\n\nEach command has preconditions.\n\n| Command | Example preconditions |\n|---|---|\n`add_item` |\nProduct identity known, actor may mutate cart, cart is mutable |\n`revalidate_cart` |\nCart exists, relevant facts can be refreshed or checked |\n`prepare_checkout` |\nPrice, inventory, policy coverage, shipping, buyer context, generated-claim constraints, and cart snapshot are valid |\n`attach_payment_authority` |\nCheckout exists, actor context is known, authority artifact is present |\n`request_payment` |\nCheckout prepared, payment authority valid, cart snapshot matches, amount and currency are in scope |\n`commit_order` |\nPayment result satisfies order rules, cart snapshot is still valid, order creation is idempotent |\n\nA command result should report the transition outcome:\n\n```\ntype CheckoutCommandRequest = {\n  command: CheckoutCommand;\n  checkoutId: string;\n  actorId: string;\n  idempotencyKey: string;\n  context: {\n    region?: string;\n    buyerType?: \"consumer\" | \"business\";\n    channel: \"storefront\" | \"agent\" | \"marketplace\";\n    currency?: string;\n  };\n  payload: unknown;\n};\n\ntype CheckoutCommandResult = {\n  accepted: boolean;\n  previousState: CheckoutState;\n  nextState: CheckoutState;\n  blockers: Array<{\n    code: string;\n    message: string;\n    nextAction?: string;\n  }>;\n  evidenceRefs: string[];\n  auditEventId: string;\n};\n```\n\nThis is different from returning only an HTTP success or failure.\n\nThe HTTP request may succeed because the platform processed the command. The checkout command may still be rejected because the transition was not valid.\n\nThat distinction is important for agents. A blocked transition is not an infrastructure error. It is a domain answer.\n\nA checkout transition should not collapse eligibility, authority, and state.\n\nThey answer different questions.\n\nEligibility asks:\n\n```\nIs this action valid for this product, cart, buyer, region, channel, and current facts?\n```\n\nAuthority asks:\n\n```\nIs this actor allowed to request this action?\n```\n\nState transition asks:\n\n```\nGiven the current checkout state, can this command move the checkout to a new state?\n```\n\nAll three can fail independently.\n\nA product may be eligible for checkout, but the checkout session may be expired.\n\nAn actor may have authority to request checkout preparation, but the cart may be blocked because policy coverage is incomplete.\n\nA checkout may be prepared, but delegated payment authority may be missing.\n\nA payment authority may be present, but the cart snapshot may no longer match.\n\nA useful execution path is:\n\n```\nCommand received\n      ↓\nCurrent checkout state loaded\n      ↓\nCommercial facts selected\n      ↓\nEligibility evaluated\n      ↓\nAuthority evaluated\n      ↓\nTransition preconditions evaluated\n      ↓\nState transition applied or blocked\n      ↓\nEvidence and audit recorded\n```\n\nThis is where the Part 1 model becomes operational.\n\n```\nFacts → Eligibility → Authority → State transition → Evidence → Audit\n```\n\nCheckout is the first article in the series where the full chain becomes unavoidable.\n\nCart mutation is sometimes treated as low risk because the buyer has not paid yet.\n\nThat assumption is weak in agent-facing commerce.\n\nA 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.\n\nThe platform needs to distinguish mutable carts from validated snapshots.\n\nA simple cart state model might be:\n\n```\ntype CartState =\n  | \"mutable\"\n  | \"requires_revalidation\"\n  | \"validated\"\n  | \"locked_for_checkout\"\n  | \"expired\";\n```\n\nIn `mutable`\n\n, items can be added or removed.\n\nIn `requires_revalidation`\n\n, a cart change or stale fact means prior readiness can no longer be trusted.\n\nIn `validated`\n\n, the platform has checked the relevant facts for the current context.\n\nIn `locked_for_checkout`\n\n, the cart snapshot is being used for checkout preparation or payment authority.\n\nIn `expired`\n\n, the cart can no longer support payment or order commitment without revalidation.\n\nFor the Travel Backpack:\n\n```\nadd_item:\nrequires revalidation because inventory is stale\n\nprepare_checkout:\nblocked because inventory is stale and return-policy coverage is missing\n```\n\nThe platform should not silently move from item addition to checkout readiness. The transition should be explicit.\n\nCheckout needs snapshots because mutable carts are not stable enough for payment authority or order commitment.\n\nA cart ID identifies a container. It does not prove that the cart contents, price, currency, shipping region, taxes, policies, or total remain the same.\n\nFor agent-ready commerce, this matters because delegated payment usually depends on the buyer authorizing a bounded action.\n\nFor example:\n\n```\nBuy one Travel Backpack for up to €150 in EUR from this merchant.\n```\n\nThat authority should apply to a specific commercial snapshot, not to any future contents of the same cart ID.\n\nA cart snapshot might include:\n\n```\ntype CartSnapshot = {\n  snapshotId: string;\n  cartId: string;\n  items: Array<{\n    sku: string;\n    quantity: number;\n    priceAmount: number;\n    currency: string;\n  }>;\n  context: {\n    region?: string;\n    buyerType?: \"consumer\" | \"business\";\n    channel: \"storefront\" | \"agent\" | \"marketplace\";\n  };\n  totals: {\n    subtotal: number;\n    shipping?: number;\n    tax?: number;\n    total: number;\n    currency: string;\n  };\n  factRefs: string[];\n  policyFactRefs: string[];\n  createdAt: string;\n};\n```\n\nA payment authority should reference the snapshot it applies to:\n\n```\ntype PaymentAuthorityRef = {\n  authorityId: string;\n  cartSnapshotId: string;\n  maxAmount: number;\n  currency: string;\n  merchantId: string;\n  expiresAt: string;\n};\n```\n\nBefore payment, the platform should check:\n\n```\nDoes the current cart snapshot match the authorized snapshot?\nIs the amount within scope?\nIs the currency unchanged?\nIs the merchant unchanged?\nHas the authority expired?\nHas the authority been revoked?\n```\n\nThis prevents a serious failure mode: the buyer authorizes one commercial state and the platform charges for another.\n\nA mutable cart is not a mandate. A snapshot is closer to the boundary the platform can reason about.\n\n`prepare_checkout`\n\nshould not mean “create a checkout URL if possible.”\n\nIt should mean:\n\n```\nEvaluate whether this cart can enter a checkout-prepared state for this buyer context.\n```\n\nThat transition should require:\n\n```\nProduct identity\nPrice freshness\nInventory freshness\nPolicy coverage\nShipping region\nBuyer type\nChannel\nGenerated-claim constraints\nTax and total calculation readiness\nCart snapshot integrity\nActor authority for preparation\n```\n\nFor the Travel Backpack, `prepare_checkout`\n\nshould be blocked:\n\n```\nReasons:\n- inventory is stale\n- return-policy coverage is missing for Travel Bags\n```\n\nA structured result might look like this:\n\n``` js\nconst prepareCheckoutResult = {\n  command: \"prepare_checkout\",\n  previousState: \"cart_requires_revalidation\",\n  nextState: \"cart_blocked\",\n  accepted: false,\n  blockers: [\n    {\n      code: \"INVENTORY_STALE\",\n      message: \"Inventory must be revalidated before checkout can be prepared.\",\n      nextAction: \"Revalidate inventory from the inventory source.\"\n    },\n    {\n      code: \"RETURN_POLICY_MISSING\",\n      message:\n        \"Return-policy coverage is missing for the Travel Bags category.\",\n      nextAction:\n        \"Attach or approve return-policy coverage for Travel Bags.\"\n    }\n  ],\n  evidenceRefs: [\n    \"truth:BAG-TRAVEL-42:inventory\",\n    \"policy:travel_bags:returns\"\n  ]\n};\n```\n\nThat response gives the agent a clear boundary. It gives operators actionable remediation. It gives audit a reason.\n\nA boolean such as `checkoutReady: false`\n\ndoes not do that.\n\nSome checkout systems perform their strictest validation late, near payment or order placement.\n\nThat is too late for agent-facing commerce.\n\nFinal validation still matters. The platform should always recheck critical facts before payment or order commitment. But obvious blockers should be detected earlier.\n\nIf return-policy coverage is missing, the platform should not wait until payment to fail.\n\nIf inventory is stale, the platform should not ask for delegated payment first and then discover the cart cannot be prepared.\n\nIf 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.\n\nEarlier transitions should fail earlier.\n\nFor the Travel Backpack, the explainable sequence is:\n\n```\nquote_policy:\nblocked for returns\n\nprepare_checkout:\nblocked because inventory is stale and return-policy coverage is missing\n\ndelegate_payment:\nblocked because checkout is not valid\n```\n\nA late failure such as:\n\n```\npayment_failed\n```\n\ndoes not explain the real domain issue.\n\nA better checkout model preserves the real blocker:\n\n```\ncheckout_not_valid:\ninventory stale\nreturn-policy coverage missing\n```\n\nAgents 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.\n\nDelegated payment depends on checkout state, but it should not be collapsed into checkout state.\n\nA checkout can be prepared and still lack payment authority.\n\nA payment authority can be present and still not match the cart snapshot.\n\nA payment authority can be valid for one amount and invalid for another.\n\nA payment authority can expire or be revoked before use.\n\nA simplified payment-related state path might look like this:\n\n```\ncheckout_prepared\n      ↓\npayment_authority_required\n      ↓\npayment_authority_validated\n      ↓\npayment_pending\n      ↓\norder_committed\n```\n\nFor delegated payment, the platform should verify:\n\n```\ncheckout session is valid\ncart snapshot matches\namount is within the mandate\ncurrency matches\nmerchant matches\nauthority has not expired\nauthority has not been revoked\nactor is allowed to use the authority\nrequired confirmation has been satisfied\n```\n\nThis should happen before payment is requested.\n\nThe adapter may carry AP2-related or other payment-protocol artifacts. Those artifacts are inputs. They are not permission by themselves.\n\nThis continues the boundary from Part 5:\n\n```\nProtocol validity is not payment authority.\nPayment authority is not checkout validity.\nCheckout validity is not order commitment.\n```\n\nA state machine makes those boundaries visible.\n\nCheckout state should expire.\n\nPrices 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.\n\nExpiry should be part of the checkout model, not a hidden timestamp checked inconsistently across handlers.\n\n```\ntype CheckoutSession = {\n  checkoutId: string;\n  state: CheckoutState;\n  cartSnapshotId: string;\n  preparedAt?: string;\n  expiresAt?: string;\n  blockers: string[];\n};\n```\n\nIf a session expires, later commands should not treat it as current.\n\nFor example:\n\n```\nrequest_payment on expired checkout:\nblocked\n\nReason:\nCheckout session expired. Revalidation is required before payment can be requested.\n```\n\nThis 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.\n\nThe platform should not rely on the agent to remember expiry. It should enforce expiry at the state boundary.\n\nAgent-facing systems must assume retries.\n\nA 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.\n\nRetry behavior should be designed, not discovered in production.\n\nEach state-changing command should carry an idempotency key:\n\n```\ntype CheckoutCommandEnvelope = {\n  commandId: string;\n  idempotencyKey: string;\n  command: CheckoutCommand;\n  checkoutId: string;\n  requestedAt: string;\n};\n```\n\nIf the same command is retried, the platform should return the same result when appropriate instead of creating a duplicate transition.\n\nExample:\n\n```\nFirst request:\nprepare_checkout creates checkout session chk_123.\n\nNetwork response:\nlost.\n\nRetry with same idempotency key:\nreturns checkout session chk_123, not a second checkout session.\n```\n\nIdempotency belongs where the state transition occurs.\n\nA protocol adapter can pass the key. The checkout service must enforce it.\n\nIf idempotency exists only in the adapter, duplicate transitions can still happen through another adapter, feed-triggered workflow, admin action, or retry path.\n\nCheckout is also vulnerable to concurrent changes.\n\nA 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.\n\nA state machine does not eliminate concurrency problems, but it gives the platform a place to handle them.\n\nUseful techniques include:\n\n```\nCart snapshot identifiers\nOptimistic concurrency checks\nState transition guards\nIdempotency keys\nExpiry timestamps\nFinal validation before payment or order commitment\n```\n\nFor example, `request_payment`\n\nshould fail if the cart snapshot used by the payment authority is not the current checkout snapshot.\n\n```\nrequest_payment:\nblocked\n\nReason:\nCart snapshot changed after payment authority was granted.\n```\n\nThis is not a generic technical conflict. It is a commercial safety rule.\n\nThe buyer authorized one thing. The cart now represents something else. The platform must not proceed silently.\n\nA checkout transition should record the evidence used to allow or block it.\n\nFor an allowed transition, evidence explains what the platform relied on.\n\nFor a blocked transition, evidence explains why the platform refused.\n\nA `checkout_prepared`\n\ntransition may reference:\n\n```\nprice fact\ninventory validation\npolicy coverage facts\nshipping calculation\ntax calculation\ncart snapshot\neligibility decision\nauthority decision\n```\n\nA blocked `prepare_checkout`\n\ntransition for the Travel Backpack may reference:\n\n```\ninventory stale fact\nmissing return-policy coverage\ncurrent cart snapshot\nbuyer context\neligibility decision\n```\n\nA transition record might look like this:\n\n```\ntype CheckoutTransitionRecord = {\n  transitionId: string;\n  checkoutId: string;\n  command: CheckoutCommand;\n  previousState: CheckoutState;\n  nextState: CheckoutState;\n  accepted: boolean;\n  actorId: string;\n  idempotencyKey: string;\n  evidenceRefs: string[];\n  blockers: string[];\n  occurredAt: string;\n};\n```\n\nThis record is useful for more than compliance. It supports agent responses, operator remediation, debugging, replay analysis, and customer support.\n\nWhen checkout is blocked, the platform should not have to reconstruct the reason from logs. The transition should already contain the reason.\n\nAn audit log that says “checkout failed” is not enough.\n\nFor agent-ready checkout, audit should capture the decision path:\n\n```\nWho or what requested the command\nWhich protocol or channel was used\nWhich cart snapshot was evaluated\nWhich product facts were used\nWhich policy facts were used\nWhich eligibility decision was produced\nWhich authority decision was produced\nWhich state transition was attempted\nWhether the transition was accepted or blocked\nWhich blockers were returned\nWhich idempotency key was used\nWhich evidence records were attached\n```\n\nThis allows the platform to answer questions such as:\n\n```\nWhy did the agent say checkout could not proceed?\nWas inventory stale?\nWas a return policy missing?\nDid the buyer authorize payment?\nDid the cart change after authorization?\nDid a retry create a duplicate operation?\nWhich adapter initiated the request?\nWhich transition produced the final state?\n```\n\nAudit is not a logging afterthought. It is part of the safety model.\n\nThe Part 1 chain ends with audit because agent-facing actions need explainable history.\n\nA blocked checkout transition should create or update operator tasks when the blocker is actionable.\n\nFor the Travel Backpack:\n\n```\nprepare_checkout:\nblocked\n\nBlockers:\n- INVENTORY_STALE\n- RETURN_POLICY_MISSING\n```\n\nThe platform can produce tasks:\n\n```\nTask 1:\nRevalidate inventory for BAG-TRAVEL-42.\n\nImpact:\nAgents can discover and compare the product, but checkout preparation is blocked.\n\nTask 2:\nAttach or approve return-policy coverage for Travel Bags.\n\nImpact:\nAgents cannot quote return terms and checkout preparation is blocked if complete policy coverage is required.\n```\n\nThis is better than leaving errors inside checkout logs.\n\nOperator 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.\n\nThe checkout state machine becomes one source of evidence for that operational layer.\n\nPart 5 argued that adapters should translate domain decisions rather than own them. Checkout is where that boundary becomes critical.\n\nA protocol adapter should not create a checkout session directly because an external request asked for one.\n\nBad boundary:\n\n``` js\nasync function adapterHandler(req: ProtocolRequest) {\n  const cart = await carts.get(req.cartId);\n  const session = await payments.createCheckoutSession(cart);\n\n  await carts.markCheckoutStarted(cart.id);\n\n  return project(session);\n}\n```\n\nThis lets the adapter perform hidden transitions.\n\nBetter boundary:\n\n``` js\nasync function adapterHandler(req: ProtocolRequest) {\n  const command = mapToCheckoutCommand(req);\n  const result = await checkout.handleCommand(command);\n\n  return projectCheckoutResult(result);\n}\n```\n\nThe adapter still has important work. It validates protocol shape, normalizes context, passes idempotency metadata, and projects the response.\n\nBut the checkout domain owns the mutation.\n\nThat prevents ACP, MCP, AP2, internal feeds, and future adapters from creating different checkout paths.\n\nA checkout state machine can also be overbuilt.\n\nIt should not become the place where every commerce rule is implemented directly.\n\nA useful boundary is:\n\n```\nCommercial truth service:\nProvides source-backed product facts.\n\nPolicy service:\nProvides applicable policy facts and quoteability.\n\nEligibility service:\nDecides whether actions are valid.\n\nAuthority service:\nDecides whether the actor may request the action.\n\nCheckout state machine:\nControls checkout states and transitions.\n\nPayment service:\nHandles payment-specific operations.\n\nAudit service:\nRecords decisions and transitions.\n```\n\nThe checkout state machine coordinates these decisions at the mutation boundary. It should not duplicate all of them internally.\n\nIf checkout reimplements eligibility differently from the feed, policy quotation, or admin readiness views, the platform reintroduces the semantic drift discussed in Part 5.\n\nThe state machine should consume shared domain decisions and then decide whether a transition can occur from the current state.\n\nA checkout state machine should use state names that describe commercial meaning, not UI steps or handler names.\n\nWeak state names:\n\n```\nstep_1\nstep_2\npayment_screen\nsubmit_clicked\nhandler_complete\n```\n\nThese describe implementation flow. They are not useful for agents, operators, or audit.\n\nBetter state names:\n\n```\ndraft_cart\ncart_requires_revalidation\ncart_blocked\ncheckout_ready\ncheckout_prepared\npayment_authority_required\npayment_pending\norder_committed\nexpired\ncancelled\nfailed\n```\n\nThese names communicate what the platform believes about the checkout.\n\nA useful test is whether an operator or support engineer can understand the state without reading the UI code.\n\nIf the state name only makes sense as a page in a checkout form, it is probably not the right state for agent-ready commerce.\n\nThere is also a risk of over-modeling.\n\nA platform should not create a persistent state for every internal check.\n\nFor example:\n\n```\nchecking_price\nchecking_inventory\nchecking_policy\nchecking_tax\nchecking_shipping\nchecking_authority\n```\n\nThese may be internal steps inside a transition, not durable states.\n\nA practical rule:\n\n```\nMake it a state when different commands are allowed, blocked, required, retried, expired, or audited differently.\nKeep it as transition logic when it is only an internal validation step.\n```\n\nFor example, `cart_requires_revalidation`\n\nis useful because it changes what can happen next. `checking_policy`\n\nmay not be useful unless the system genuinely waits for asynchronous policy review.\n\nGood state-machine design is not about maximizing the number of states. It is about naming the boundaries that affect behavior.\n\nExplicit checkout state improves safety, but it introduces its own failure modes.\n\nIf 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.\n\nState should represent commercial readiness, not UI layout.\n\nA model with only `active`\n\n, `submitted`\n\n, and `complete`\n\nhides important differences.\n\nA 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.\n\nToo few states recreate the original problem.\n\nA 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.\n\nPersistent states should represent meaningful business boundaries, not every validation sub-step.\n\nIf protocol adapters mutate checkout state directly, the state machine is bypassed. Different adapters may create different checkout paths.\n\nAll state changes should go through the checkout command boundary.\n\nCheckout should consume eligibility decisions, not reimplement separate rules that contradict feeds, policy quotation, or admin surfaces.\n\nSome final validation belongs in checkout, but the decision model should remain consistent.\n\nPayment authority should not be reduced to `checkout.authorized = true`\n\n.\n\nAuthority has scope, expiry, revocation, amount limits, currency, merchant binding, actor identity, and confirmation requirements. Checkout can reference authority decisions. It should not oversimplify them.\n\nIf payment authority references a mutable cart, the buyer may authorize one thing and the platform may charge for another.\n\nSnapshot identity should be explicit.\n\nIf retries are not idempotent, duplicate checkout sessions, payment attempts, or order commits can occur.\n\nIdempotency should be enforced at the state-transition boundary.\n\nIf checkout expiry is hidden in timestamps or external payment sessions, the platform may accept stale transitions.\n\nExpiry should be part of checkout state behavior.\n\nBlocked transitions are as important as successful transitions. They explain why agents could not proceed and what operators need to fix.\n\nAudit should record attempted commands, decisions, blockers, evidence, and state transitions.\n\nCheckout state should be tested with scenario matrices, not only endpoint tests.\n\nA useful test includes:\n\n```\nInitial state\nCommand\nCommercial facts\nPolicy coverage\nBuyer context\nAuthority context\nExpected next state\nExpected blockers\nExpected audit event\nExpected idempotency behavior\n```\n\nFor the Travel Backpack:\n\n| Scenario | Expected result |\n|---|---|\n| Add Travel Backpack to draft cart with stale inventory | Cart enters `cart_requires_revalidation`\n|\n| Prepare checkout with stale inventory and missing return policy | Transition blocked; state becomes or remains `cart_blocked`\n|\n| Revalidate inventory but return policy remains missing | Checkout remains blocked for policy coverage |\n| Add return-policy coverage but US shipping remains unknown for US buyer | US checkout blocked or requires review |\n| EU buyer, fresh inventory, complete policy coverage | Checkout can move to `checkout_prepared`\n|\n| Delegated payment requested without authority | State moves to or remains `payment_authority_required`\n|\n| Delegated payment authority present but cart snapshot changed | Payment request blocked |\nRetry `prepare_checkout` with same idempotency key |\nSame result returned; no duplicate checkout session |\nRetry `commit_order` after ambiguous response |\nExisting order result returned if already committed |\n| Checkout session expired before payment | Payment request blocked; revalidation required |\n\nA simplified expectation type:\n\n```\ntype CheckoutScenarioExpectation = {\n  initialState: CheckoutState;\n  command: CheckoutCommand;\n  expectedAccepted: boolean;\n  expectedNextState: CheckoutState;\n  expectedBlockerCodes: string[];\n  expectedAuditEvent: boolean;\n};\n```\n\nThe value of these tests is not only correctness. It is explainability.\n\nWhen a scenario fails, the team can see which boundary was violated: facts, eligibility, authority, transition, evidence, or audit.\n\nA platform does not need a large workflow engine to improve checkout safety.\n\nA practical path is:\n\nName the mutation boundaries.\n\nStart with `draft_cart`\n\n, `cart_requires_revalidation`\n\n, `cart_blocked`\n\n, `checkout_ready`\n\n, `checkout_prepared`\n\n, `payment_authority_required`\n\n, `payment_pending`\n\n, `order_committed`\n\n, `expired`\n\n, and `cancelled`\n\n.\n\nMove mutations behind commands.\n\nUse commands such as `add_item`\n\n, `revalidate_cart`\n\n, `prepare_checkout`\n\n, `attach_payment_authority`\n\n, `request_payment`\n\n, `commit_order`\n\n, and `cancel_checkout`\n\n.\n\nDefine transition preconditions.\n\nFor each command, define required current state, eligibility checks, authority checks, and required facts.\n\nIntroduce cart snapshots.\n\nPayment authority and checkout preparation should reference stable cart snapshots, not only mutable cart IDs.\n\nEnforce idempotency at the transition boundary.\n\nAdapters may pass idempotency keys, but the checkout service should enforce safe retry behavior.\n\nRecord transition evidence.\n\nAllowed and blocked transitions should record facts, policy coverage, eligibility decisions, authority decisions, blockers, and audit events.\n\nKeep adapters outside mutation logic.\n\nAdapters should map protocol requests to checkout commands and project results. They should not perform hidden transitions.\n\nGenerate operator tasks from blockers.\n\nMissing policy, stale inventory, unsupported region, expired authority, and generated-claim review should produce actionable remediation when appropriate.\n\nTest scenario matrices.\n\nTest state, command, facts, authority, expected transition, blockers, idempotency, and audit together.\n\nThis path moves checkout away from loosely coupled request handlers and toward a domain model that can support agent-facing actions.\n\nA checkout state machine adds structure.\n\nIt requires named states, commands, transition rules, idempotency handling, snapshots, evidence records, audit events, and scenario tests.\n\nFor 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.\n\nAgent-ready commerce changes the threshold.\n\nOnce 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.\n\nThe tradeoff is between implementation convenience and mutation safety.\n\nRequest handlers are faster to write.\n\nState transitions are easier to reason about, test, retry, audit, and integrate across protocols.\n\nThe goal is not to make checkout complicated. The goal is to stop hiding commercial mutations inside endpoints that cannot explain what happened.\n\nCheckout is not only a form.\n\nFor agent-ready commerce, checkout is where commercial truth, policy coverage, action eligibility, actor authority, cart mutation, payment boundaries, evidence, and audit converge.\n\nThat convergence needs explicit state.\n\nThe Travel Backpack example shows why.\n\nThe 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.\n\nA state-machine model makes the boundary explicit:\n\n```\nCart mutation requires validation.\nCheckout preparation requires fresh facts and policy coverage.\nDelegated payment requires valid checkout and authority.\nRetries require idempotency.\nPayment requires a stable cart snapshot.\nBlocked transitions require evidence.\nAudit records the decision path.\n```\n\nThis keeps checkout aligned with the broader architecture model:\n\n```\nFacts → Eligibility → Authority → State transition → Evidence → Audit\n```\n\nPart 7 will move from checkout state to payment authority:\n\n**Agent-Ready Commerce, Part 7: Delegated Payment Needs More Than a Token**\n\nThat 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.\n\nWritten by [Dimitrios S. Sfyris](https://gr.linkedin.com/in/dimitrios-s-sfyris), Founder & Software Architect at [AspectSoft](https://aspectsoft.gr/en/).\n\nAspectSoft designs and develops custom software platforms, e-commerce systems, SaaS infrastructure, integrations, analytics tools, and practical digital products.\n\nYou can also follow the [AspectSoft LinkedIn page](https://gr.linkedin.com/company/aspectsoft) for updates on software platforms, commerce systems, AI tooling, and developer-focused products.", "url": "https://wpnews.pro/news/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form", "canonical_source": "https://dev.to/dmsfiris/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form-4b52", "published_at": "2026-06-29 07:38:21+00:00", "updated_at": "2026-06-29 07:57:16.487115+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form", "markdown": "https://wpnews.pro/news/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form.md", "text": "https://wpnews.pro/news/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form.txt", "jsonld": "https://wpnews.pro/news/agent-ready-commerce-part-6-checkout-is-a-state-machine-not-a-form.jsonld"}}