POST /orders reference

Implementation notes and runnable test cases for the order-placement endpoint. Each case fires a real request and explains the backend branch it triggers.

POST/ordersreserve → charge → confirm saga

Places an order: validates the cart, geocodes the destination, picks the nearest warehouse that can fill every line item, reserves inventory under a row lock, charges the card outside the lock, and confirms the order. The cases below run real requests against the API and explain exactly which branch each one exercises.

(NEXT_PUBLIC_API_URL)

Implementation: the saga, step by step

0

Idempotency replay

If an Idempotency-Key header is present and already maps to an order, return that order immediately. Duplicate product lines in the body are also rejected here (they would defeat the per-line stock check).

1

Validate products & compute total

Load every productId in one findMany. A length mismatch → 404. The order total is computed up front in integer cents (Math.round(price*100)) to avoid binary-float drift, so it is available even for a later-failing attempt.

2

Geocode the shipping address

GeocodingService turns the address into { lat, lng }. Runs only after cheap validation passes, so malformed orders never pay the geocoding cost.

3

Rank eligible warehouses

findEligibleByDistance() returns warehouses that can fill EVERY line item from one location, sorted by haversine distance. Empty list → 422 (no single-warehouse fulfilment).

4

Reserve (short transaction)

For each warehouse nearest-first: lock inventory rows FOR UPDATE, re-validate quantities, decrement, and create the Order as PENDING. A lost stock race falls through to the next warehouse. Locks are held only for this brief transaction, never across payment.

5

Charge the card (no locks held)

PaymentService.charge() runs outside any transaction. A card starting with 0000 is declined. On decline, releaseReservation() restores inventory and marks the order PAYMENT_FAILED.

6

Confirm PENDING → PAID

An atomic updateMany guarded by status: PENDING flips the order to PAID. If the row is no longer PENDING (the reaper expired it past the TTL while payment was in flight), the charge is refunded and a 409 is returned.

Executable test cases for POST /orders

Open DevTools > Network tab first to inspect outgoing requests.

POST/orders201 Created
POST/orderstwo requests, same order id
POST/orders5 parallel requests
POST/orders422 Unprocessable Entity
POST/orders422 Unprocessable Entity
POST/orders404 Not Found
POST/orders400 Bad Request