Zepto system design: one grocery order, from basket to rider

Follow one grocery basket through search, an inventory hold, UPI payment, an order saga with an outbox, packing and rider dispatch.

Successful order

  1. You tap add to cart, and the clock starts: ten minutes, door to door.
  2. A load balancer picks a healthy server, and the API gateway checks your login and rate limit.
  3. First: can anyone reach you in ten minutes? Serviceability finds the dark store that covers your address.
  4. Search looks only in that store’s own index, and Enrichment adds the live price and stock.
  5. Your basket comes back: three items, 184 rupees, all in stock.
  6. Placing the order starts a checkout saga: a chain of saved steps, so a failure resumes instead of starting over.
  7. Its draft is saved to DynamoDB once, so a double tap never makes two checkouts.
  8. Inventory holds your items for ten minutes, in one atomic step, so nobody grabs the last pack of eggs while you pay.
  9. Payment asks your UPI app for 184 rupees, through the payment provider.
  10. The money is captured, but Payment trusts only the provider’s signed callback, never the phone.
  11. Only then is the hold made final, and the Order service creates exactly one order.
  12. The order and an outbox row commit in one transaction, so the event that starts packing is never lost.
  13. The reply goes back the way it came: order placed.
  14. Meanwhile, the outbox publishes an Order Confirmed event to Kafka.
  15. Fulfilment creates a picking task. A picker scans all three items and seals the bag.
  16. With the bag sealed, Delivery asks for a rider.
  17. Dispatch asks Redis which riders are near the store right now, and ranks them by pickup time.
  18. The quickest one gets the offer, and accepts.
  19. Dispatch saves one assignment, so two riders can never claim the same bag.
  20. Delivery now has a sealed bag and a rider, and verifies the pickup.
  21. Your phone is told: on the way, nine minutes. Eighteen systems and two people, for one basket.

Payment fails · retry

  1. Same basket, same ten-minute promise. But this time, watch the payment.
  2. A load balancer picks a healthy server, and the API gateway checks your login and rate limit.
  3. First: can anyone reach you in ten minutes? Serviceability finds the dark store that covers your address.
  4. Search looks only in that store’s own index, and Enrichment adds the live price and stock.
  5. Your basket comes back: three items, 184 rupees, all in stock.
  6. Placing the order starts a checkout saga: a chain of saved steps, so a failure resumes instead of starting over.
  7. Its draft is saved to DynamoDB once, so a double tap never makes two checkouts.
  8. Inventory holds your items for ten minutes, in one atomic step, so nobody grabs the last pack of eggs while you pay.
  9. Payment asks your bank for 184 rupees, and the bank says no: insufficient funds.
  10. Payment records the failed attempt, but keeps the hold, so nobody else can take your eggs.
  11. Your phone shows the decline. Nothing was charged, and your items are still held.
  12. You tap retry, and the same checkout resumes against the same hold. Nothing starts over.
  13. It’s a new attempt with its own ID, and only one can be live at a time, so you’re never charged twice. This one is captured.
  14. Payment checks the signed callback, the hold is made final, and exactly one order is created.
  15. The order and its outbox row are saved together, and your phone shows: order placed.
  16. Meanwhile, the outbox publishes an Order Confirmed event to Kafka.
  17. Fulfilment creates a picking task. A picker scans all three items and seals the bag.
  18. With the bag sealed, Delivery asks for a rider.
  19. Dispatch asks Redis which riders are near the store right now, and ranks them by pickup time.
  20. The quickest one gets the offer, and accepts.
  21. Dispatch saves one assignment, so two riders can never claim the same bag.
  22. Pickup is verified, and your phone is told: on the way. One declined payment, one retry, and you paid exactly once.

Items missing at pickup

  1. Same basket, same ten-minute promise. This time, watch what happens in the store.
  2. A load balancer picks a healthy server, and the API gateway checks your login and rate limit.
  3. First: can anyone reach you in ten minutes? Serviceability finds the dark store that covers your address.
  4. Search looks only in that store’s own index, and Enrichment adds the live price and stock.
  5. Your basket comes back: three items, 184 rupees, all in stock.
  6. Placing the order starts a checkout saga: a chain of saved steps, so a failure resumes instead of starting over.
  7. Its draft is saved to DynamoDB once, so a double tap never makes two checkouts.
  8. Inventory holds your items for ten minutes, in one atomic step, so nobody grabs the last pack of eggs while you pay.
  9. Payment asks your UPI app for 184 rupees, through the payment provider.
  10. The money is captured, but Payment trusts only the provider’s signed callback, never the phone.
  11. Only then is the hold made final, and the Order service creates exactly one order.
  12. The order and an outbox row commit in one transaction, so the event that starts packing is never lost.
  13. The reply goes back the way it came: order placed.
  14. Order Confirmed reaches Kafka, and a picker starts scanning. Milk, bread, and then the eggs: the shelf is empty.
  15. The picker reports it. Fulfilment records the shortage and publishes an Item Unavailable event to Kafka.
  16. The Order service amends your order: version two, 84 rupees, saved with a new outbox row.
  17. Now the saga compensates, walking backwards: Inventory voids the eggs line, and Payment asks the provider to refund exactly 100 rupees.
  18. The refund is recorded, and only then is your phone told: 100 rupees refunded.
  19. Meanwhile, the picker sealed the two items that exist, and Delivery asks for a rider.
  20. Dispatch asks Redis which riders are near the store right now, and ranks them by pickup time.
  21. The quickest one gets the offer, and accepts.
  22. Dispatch saves one assignment, so two riders can never claim the same bag.
  23. Pickup is verified, and your phone is told: on the way, two items. You pay only for what arrives.