Skip to content
Innovialabs

Order Processing With Sagas: Consistent Payment, Stock and Delivery

Advanced4 min

In short

A saga splits an order into local steps with compensating actions, while an outbox and an order state machine ensure no event is lost and no order gets stuck in a contradictory state.

How sagas solve consistency

When payment, inventory and shipping are separate services or external systems, there is no single transaction covering them all. A saga is a chain of local transactions where each step has a compensating action: if step 3 fails, steps 2 and 1 are undone in reverse order.

A typical order saga:

StepActionCompensation
1Create order as pendingCancel order
2Reserve stockRelease reservation
3Charge paymentRefund
4Create shipmentCancel shipment
5Confirm order—

The result is eventual consistency: the system may briefly sit in an intermediate state, but it always reaches a correct final one.

Why distributed orders break

  • Partial success. The card was charged, but the reservation wasn't created because the warehouse timed out.
  • Dual writes. A service saved the order but crashed before publishing the event, so nobody else knows about it.
  • Retries. The network retried a request and the payment went through twice.
  • Unknown outcome. A timeout is not a failure — the payment may have succeeded.
  • Event ordering. A cancellation arrived before the confirmation.

Two-phase commit (2PC) across external APIs, payment providers and couriers is usually not available, which is why sagas exist.

Orchestration vs choreography

Orchestration — a central coordinator stores the saga's state and calls services one by one.

Choreography — services react to each other's events: OrderCreated → inventory reserves → StockReserved → payment charges, and so on.

CriterionOrchestrationChoreography
Where the process logic livesIn one placeSpread across services
ObservabilityEasier: saga state is visibleRequires event tracing
CouplingServices depend on the orchestratorServices depend on events
Best forLong processes with branchingShort chains of 2-3 steps

For an order with payment, stock and delivery, orchestration is usually easier to maintain.

The order state machine

An order needs explicit statuses and allowed transitions. A forbidden transition is an error, not a silent update.

pending -> stock_reserved -> paid -> shipping_created -> confirmed
pending | stock_reserved | paid -> compensating -> cancelled

This guards against races: if PaymentSucceeded arrives for an order already cancelled, the system doesn't revive it — it triggers a refund.

Transactional outbox

To avoid losing events on a crash, the state change and the event are written in the same local transaction: to the orders table and to an outbox table. A separate process reads the outbox and publishes messages to the broker.

BEGIN;
UPDATE orders SET status = 'stock_reserved' WHERE id = $1;
INSERT INTO outbox (aggregate_id, type, payload)
VALUES ($1, 'StockReserved', $2);
COMMIT;

The outbox gives at-least-once delivery, so every consumer must be idempotent: store processed message ids or pass an idempotency key to the payment API.

Failure scenarios

  • Payment declined. Compensate: release stock, move the order to cancelled, notify the customer.
  • Payment timeout. Don't cancel right away — query the provider for status or wait for the webhook, then decide.
  • Courier API down. Retry with backoff; after the limit, hand off to manual handling rather than auto-refunding.
  • A compensation fails. Retry until it succeeds and raise an alert; compensations must be idempotent.
  • Duplicate message. An idempotent handler ignores the repeat.

Common mistakes

  • Calling external APIs inside a database transaction.
  • Treating a timeout as a failure.
  • No manual intervention path for stuck sagas.
  • Not logging saga steps with a shared correlation id.

FAQ

If the monolith calls external payment and logistics APIs, the problem is the same: a local transaction doesn't cover the outside world. A state machine and an outbox are useful in a monolith too.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.