Skip to content

State machines and workflows

A state machine protects one aggregate transition. A workflow coordinates several aggregates and external systems.

A state machine protects one aggregate transition. A workflow coordinates several aggregates and external systems. Adocommerce Kit uses both because checkout needs both kinds of safety.

State machines use conditional writes

A transition is defined by allowed source states, one destination, and optional guards. Persistence uses a conditional update:

UPDATE commerce_orders
SET state = 'paid'
WHERE id = ? AND state = 'placed'

Zero updated rows means the state was stale or invalid, so the service throws E_STATE_TRANSITION. It does not read a state, decide in memory, and write later.

Orders, payment intents, payments, refunds, shipments, and reservations expose explicit state vocabularies. Controllers call services; they do not assign state columns.

Checkout is a compensating workflow

The default place-order workflow performs these responsibilities in order:

  1. Validate and reprice the cart.
  2. Lock the active cart and move it to checking_out.
  3. Reserve inventory with guarded SQL updates.
  4. Create and confirm the payment intent.
  5. Create the order ledger.
  6. Finalize the order, payment, reservation, and cart state.
  7. Emit transactional events.

Steps that acquire resources define compensation. A later failure can void a payment intent, release reservations, and return the cart to active. Compensation runs in reverse order and its report is attached to E_WORKFLOW_FAILED.

Idempotency closes the retry gap

Checkout claims an idempotency row before workflow execution. The claim stores a canonical request hash.

  • Same key, same completed request: return the stored order.
  • Same key, different request: E_IDEMPOTENCY_CONFLICT.
  • Same key while another request is running: a retry-later conflict.
  • No key: derive one from cart identity and update timestamp.

This protocol prevents a browser retry from creating a second order or second payment.

Recovery handles process death

Compensation cannot run if the process dies. ReconcileStuckCheckouts finds carts left in checking_out beyond the configured grace period.

  • Authorized or captured intent plus an existing order: resume finalization.
  • Otherwise: void what can be voided, release stock, and reactivate the cart.

The recovery job is idempotent. It shares the same services and guarded transitions as the synchronous path.

Customize in the workflow, not around it

PlaceOrderWorkflow supports insertBefore, insertAfter, replace, and remove. Register changes in a provider after resolving the singleton. A custom step receives the workflow context, including the cart, input, transaction, order, intent, and payment.

Every step that creates a durable side effect should have a compensation strategy or a clear reason it is safe to repeat. See Add a workflow step.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close