3 min readRishi

Designing a Shopping Cart: Price Snapshots, Merge, and No Inventory Hold

Designing a Shopping Cart: Price Snapshots, Merge, and No Inventory Hold

Adding a shirt to a cart is not booking a seat. The cart is a small document keyed by a person, written often, read by that person, and thrown away at checkout. Treating it like inventory is how you hold stock for someone who will never come back. The hold belongs later, and it has a deadline.

What a line stores

A line is a SKU, a quantity, a currency, and the unit price at the time it was added. The price on the line is a snapshot, not a promise. Inventory is not on the line. Availability shown next to the button is a read of the inventory service, and it is allowed to be stale by the time the user looks at the cart again.

The cart itself is one partition: user id, or an anonymous cart id, as the key. You do not need a distributed transaction to increment a quantity. Two tabs adding the same SKU should land on one line. The write is "set this SKU's quantity," or "add delta," applied in the cart service so a double-click does not depend on the client counting.

Reprice at checkout, in front of the user

Charging the snapshot from three weeks ago is wrong when the price went up, and charging a new price with no notice is wrong when it went up too. At checkout, read the current price, compare it to the snapshot, and if the policy says the difference matters, stop and show it. The user confirms. Then you create the order.

That confirm is the moment a hold might start, if the product needs one. The hold has a TTL measured in minutes, and it returns stock if payment does not finish. How that hold survives contention is the problem in booking inventory. The cart does not start the hold on add-to-cart. Abandoned carts would pin the warehouse.

The order created from the cart is a write the client will retry. Give that POST an idempotency key so a timed-out checkout does not create two orders. The money side of that order is a ledger, covered in double-entry payments. The cart row can be deleted, or marked checked out, in the same step that accepts the key. A retry returns the original order.

Anonymous carts and the login merge

Before login the cart id is a random value in a cookie, not a sequence. A sequence is guessable, and a guessable cart id is someone else's basket.

On login, merge once. Same SKU: add the quantities, cap at whatever maximum you sell. Then reprice. The merge itself has to be idempotent. A login callback that runs twice, or a user with two devices, must not double the quantities a second time. Record that this anonymous cart id has already been merged into this user, and make the second attempt a no-op that returns the current cart.

Anonymous carts expire. A week is plenty. A logged-in cart can live until checkout or until you decide the snapshot is too old to show without a reprice. The document is small enough for a single row or a Redis hash per user. The interesting failures are the merge and the price change, not the storage engine.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

  • No comments yet. Be the first.