Skip to content
Codeloom
System Design

System Design: Design an E-Commerce Checkout System

Design a checkout system that handles cart management, inventory reservation, payment orchestration, and order fulfillment. Covers saga patterns and consistency under high load.

·9 min read · By Codeloom
Intermediate 12 min read

What you'll learn

  • Design a cart and inventory reservation system
  • Orchestrate multi-step checkout with the saga pattern
  • Handle payment failures and partial rollbacks gracefully
  • Prevent overselling under concurrent purchases
  • Scale checkout for flash sale traffic spikes

Prerequisites

None — this post is self-contained.

The checkout flow is the most critical path in any e-commerce platform. A user adds items to a cart, enters shipping and payment details, and clicks “Place Order.” Behind that single click, the system must verify inventory, reserve stock, process payment, create the order, and kick off fulfillment — all within a few seconds, all without overselling, double-charging, or losing the order.

This article designs the system behind that flow, focusing on the coordination challenges that arise when multiple services must agree on the outcome of a single transaction.

Functional Requirements

  • Manage shopping carts (add, remove, update quantities).
  • Calculate totals including taxes, discounts, and shipping costs.
  • Reserve inventory during checkout to prevent overselling.
  • Process payments through one or more payment providers.
  • Create confirmed orders and trigger fulfillment.
  • Handle failures at any step with appropriate rollbacks.

Non-Functional Requirements

  • Availability: the checkout flow must work during traffic spikes (flash sales, Black Friday). Target 99.95% availability.
  • Consistency: never oversell inventory or double-charge a customer.
  • Latency: complete the checkout flow in under 3 seconds.
  • Idempotency: retrying a checkout request must not create duplicate orders or charges.

High-Level Architecture

The checkout system spans multiple services:

  1. Cart service — manages shopping carts in a fast data store.
  2. Pricing service — calculates item prices, taxes, discounts, and shipping.
  3. Inventory service — tracks stock levels and handles reservations.
  4. Payment service — orchestrates payment processing with external providers.
  5. Order service — creates and stores confirmed orders.
  6. Checkout orchestrator — coordinates the multi-step checkout flow.
[Client] -> [Checkout Orchestrator] -> [Cart Service]
                                    -> [Pricing Service]
                                    -> [Inventory Service]
                                    -> [Payment Service]
                                    -> [Order Service]

Cart Service

The cart is a temporary data structure that exists before the order is confirmed. Store carts in Redis for fast reads and writes.

Cart structure:

{
  "cart_id": "cart_abc",
  "user_id": "user_123",
  "items": [
    { "sku": "SKU-001", "quantity": 2, "price_snapshot": 2999 },
    { "sku": "SKU-002", "quantity": 1, "price_snapshot": 4999 }
  ],
  "updated_at": "2026-07-02T10:00:00Z"
}

Price snapshots. Store the price at the time the item was added. Re-validate prices at checkout time to catch changes. If a price has changed, notify the user before proceeding.

Cart expiration. Set a TTL on cart entries (for example, 7 days for logged-in users, 24 hours for anonymous carts). This prevents stale carts from accumulating.

Cart merging. When an anonymous user logs in, merge their anonymous cart with their existing user cart. Resolve conflicts by keeping the higher quantity for duplicate items.

Inventory Reservation

Inventory management is the hardest part of checkout. The fundamental problem: between the time a user sees “In Stock” and the time they complete checkout, someone else might buy the last item.

Two-phase inventory model:

  • Available stock: the quantity available for new purchases.
  • Reserved stock: the quantity held for in-progress checkouts.
  • Physical stock = available + reserved + already shipped.

When checkout begins, the orchestrator sends a reserve request to the inventory service. The service atomically decrements available stock and increments reserved stock:

UPDATE inventory
SET available = available - :qty,
    reserved = reserved + :qty
WHERE sku = :sku AND available >= :qty;

If the update affects zero rows, the item is out of stock. The checkout fails early with a clear message.

Reservation expiration. Reservations have a TTL (10 minutes). If checkout does not complete within the TTL, a background job releases the reservation (decrement reserved, increment available). This prevents abandoned checkouts from permanently locking inventory.

Concurrency control. The WHERE available >= :qty clause provides optimistic locking. Under extreme concurrency (flash sales), this creates contention on popular SKUs. Mitigate by:

  • Sharding inventory by SKU across database nodes.
  • Using Redis with atomic Lua scripts for hot items.
  • Pre-allocating inventory buckets: split 1000 units across 10 buckets of 100, so 10 concurrent transactions can reserve without contending.

Checkout Orchestrator and the Saga Pattern

The checkout flow involves multiple services that must either all succeed or all roll back. A traditional distributed transaction (two-phase commit) is too slow and couples all services together. Instead, use the saga pattern.

A saga is a sequence of local transactions, each with a compensating action:

StepActionCompensation
1Reserve inventoryRelease reservation
2Process paymentRefund payment
3Create orderCancel order
4Trigger fulfillmentCancel fulfillment

The orchestrator executes steps in sequence. If any step fails, it executes the compensating actions for all previously completed steps in reverse order.

Example failure scenario: Inventory is reserved (step 1 succeeds), but payment fails (step 2 fails). The orchestrator calls the inventory service to release the reservation (step 1 compensation). The user sees “Payment failed, please try again.”

Idempotency. Each step must be idempotent. The orchestrator assigns a unique checkout_id to each attempt. Services use this as an idempotency key to detect and deduplicate retried requests. If the payment service receives the same checkout_id twice, it returns the result of the first attempt rather than charging again.

Payment Processing

The payment step is the most sensitive. A charge must happen exactly once: not zero times (order without payment) and not twice (double charge).

Flow:

  1. The orchestrator sends a payment request with the checkout_id, amount, and payment method token.
  2. The payment service creates a payment record with status pending.
  3. The payment service calls the external payment provider (Stripe, Adyen) with the idempotency key.
  4. On success, update the payment record to completed and return success.
  5. On failure, update to failed and return the error.

Handling provider timeouts. If the call to the payment provider times out, the payment service does not know whether the charge went through. It must NOT retry blindly. Instead:

  • Query the provider’s API using the idempotency key to check the charge status.
  • If the charge succeeded, proceed as normal.
  • If the charge does not exist, retry the charge with the same idempotency key.

Refunds. If a later step fails and the saga must compensate, the payment service issues a refund through the provider’s API. Refunds are also idempotent (keyed by the original charge ID).

Order Creation

Once payment succeeds, the orchestrator creates the order:

{
  "order_id": "ord_789",
  "user_id": "user_123",
  "items": [...],
  "total": 10997,
  "currency": "usd",
  "payment_id": "pay_456",
  "shipping_address": {...},
  "status": "confirmed",
  "created_at": "2026-07-02T10:01:00Z"
}

The order record is the durable artifact of the checkout. It is stored in a relational database with strong consistency guarantees.

After order creation, convert the reserved inventory to “committed” (decrement reserved, no change to available since it was already decremented). Publish an order.created event to trigger downstream workflows: fulfillment, email confirmation, analytics.

Pricing and Promotions

The pricing service calculates the final order total. It runs at the beginning of checkout (to show the user the total) and again just before payment (to ensure prices have not changed).

Calculation pipeline:

  1. Look up base prices for each SKU.
  2. Apply quantity-based discounts (buy 3, get 10% off).
  3. Apply promo codes (validate the code, check usage limits, compute discount).
  4. Calculate shipping based on destination, weight, and selected speed.
  5. Calculate tax based on shipping destination (integrating with a tax API like Avalara).
  6. Return the itemized breakdown and total.

Price locking. Between showing the total and charging, prices could change. Lock the computed total by storing it as a signed, timestamped price quote with a short expiration (5 minutes). At payment time, verify the quote’s signature and expiration. If expired, recalculate and ask the user to confirm the new total.

Flash Sale Considerations

Flash sales create extreme concurrency on a small set of SKUs. Special handling is needed:

  • Inventory pre-allocation: split inventory into buckets, as described above, to reduce database contention.
  • Queue-based checkout: instead of letting all users hit the checkout simultaneously, place them in a virtual queue. Process checkouts sequentially from the queue. Show users their position and estimated wait time.
  • Rate limiting: limit checkout attempts per user to prevent bots from scooping up inventory.
  • Graceful degradation: if the system is overloaded, reject new checkout attempts with a “try again shortly” message rather than letting them time out.

Monitoring

  • Checkout conversion rate: percentage of started checkouts that result in a confirmed order. Drop signals a problem.
  • Step failure rates: failure rate at each saga step. Pinpoints which service is causing checkout failures.
  • Checkout latency: end-to-end time from “Place Order” click to order confirmation. p50 and p99.
  • Inventory reservation timeout rate: percentage of reservations that expire without order completion. High rates suggest the checkout flow is too slow or confusing.
  • Payment decline rate: percentage of payment attempts that fail. Distinguish between provider declines (card issues) and system errors.

Summary

An e-commerce checkout system is a distributed saga that coordinates inventory reservation, payment processing, and order creation across multiple services. The critical design decisions are the inventory reservation model (two-phase with TTL-based expiration), the saga orchestration pattern (sequential steps with compensating actions), and the idempotency strategy (unique checkout IDs propagated to every service). The hardest part is not the happy path but handling every possible failure mode without overselling, double-charging, or losing orders.