Sequence: Saga Compensation (Payment Failure)¶
When payment capture fails after stock has been reserved, the Orders service must compensate by releasing the reservation. Without compensation, stock would remain locked indefinitely.
sequenceDiagram
autonumber
actor Customer
participant GW as API Gateway (Kong)
participant OS as Orders Service
participant IS as Inventory Service
participant PG as Payment Gateway
participant ES as EventStoreDB
participant KB as Kafka
Customer->>GW: POST /v1/orders {items, paymentToken}
GW->>OS: POST /orders
OS->>ES: Append OrderPlaced event
OS->>KB: Publish OrderPlaced
OS->>IS: ReserveStock(orderId, items)
IS-->>OS: StockReserved (reservationId)
OS->>ES: Append OrderAwaitingPayment event
OS->>PG: CapturePayment(paymentToken, total)
PG-->>OS: PaymentFailed (reason: insufficient funds)
Note over OS: Compensation begins
OS->>IS: ReleaseReservation(reservationId)
IS->>IS: Release row lock; increment stock levels
IS-->>OS: ReservationReleased
IS->>KB: Publish StockReleased → chakra.inventory.released
OS->>ES: Append OrderCancelled event (reason: payment_failed)
OS->>KB: Publish OrderCancelled → chakra.orders.cancelled
OS-->>GW: 402 Payment Required
GW-->>Customer: 402 Payment Required
Compensation Guarantees¶
| Guarantee | Mechanism |
|---|---|
| Stock is always released on payment failure | Saga orchestrator calls ReleaseReservation before appending OrderCancelled |
| No partial state persists | EventStoreDB append is atomic; compensation is complete before the saga exits |
| Downstream consumers see consistent events | StockReleased and OrderCancelled are both published before the response is returned |
| Idempotent compensation | ReleaseReservation is idempotent — safe to retry if the call times out |