Repository navigation
feat: re-check the cart against the menu at checkout and cap quantities (#42) - #194
Merged
Merged
Conversation
…es (#42) order-service prices orders from a cart re-checked by cart-service right before checkout (POST /cart/validate). A changed price, a sold-out, archived, deleted or foreign item, or a quantity over the cap answers 409 CartChanged with details.changes, after the cart has been brought up to date, so a customer is never charged a price they did not see. Order totals come from item price x quantity in piasters, never a cart total. At most 20 of one item (MAX_CART_ITEM_QUANTITY). AppError gains optional details, sent in the error response. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Checkout re-validation (#42): an order is priced only from a cart re-checked against the menu at the moment of checkout. A customer is never charged a price they didn't see.
Closes #42.
Decision (the issue left it open): refuse and explain, rather than silently charge the current price. The customer always pays the total they were shown.
How it works
POST /cart/validate(cart-service) re-reads every cart item from menu-service.Unchanged: returns the cart.
Changed: first brings the cart up to date, then answers
409 CartChangedwithdetails.changes:PRICE_CHANGED(previousPrice,currentPrice)UNAVAILABLEREMOVEDQUANTITY_LIMITEDA renamed item is refreshed quietly: what the customer pays is the same.
A menu-service outage is an error, never a "removed" item.
order-service calls it in
createFromCartand passesCartChangedstraight to the client. The subtotal is the sum of price × quantity per line, in piasters, nevercart.total. It also re-checks each quantity is between 1 and 20. The restaurant-open check is unchanged.Quantity cap:
MAX_CART_ITEM_QUANTITY = 20per item, enforced by the cart DTOs (@Max), by adding to an existing line, and again at checkout.shared:
AppErrorgains optionaldetails, which the exception filter sends asdetails. AddsCartChangedError,CartChangeandMAX_CART_ITEM_QUANTITY.Contract and docs: public OpenAPI adds
POST /api/cart/validateand the 409CartChangedErrorschema on it and onPOST /api/orders. Docs:docs/services/cart-service.md("Checkout re-validation").The customer app side (reload the cart and quote on 409, stop + at 20) is Delivery-Plus0/delivery-plus-customer-app#47.
Test plan
CartChangedpassed on with nothing created; subtotal from the items, not the total; quantities 0, 21 and 1.5 refused; closed restaurant.npm run build(all workspaces) and the public OpenAPI generate/validate (73 paths), after rebasing on fix(order-service): today summary misses orders placed 03:00–06:00 Cairo time #192/feat: cancellation window, automatic refunds and post-pickup compensation (#53, #56) #193.npm run e2eon an isolatedbase + teststack built from this branch, all passing:409 CartChangedwithPRICE_CHANGED40 → 45;🤖 Generated with Claude Code