So far a purchase took several requests: the order was already in the database, POST /orders/{orderId}/pay charged it, and the worker sent the email later. A shop's checkout does it all in one request: the client sends the cart, and the shop creates the order, takes the payment, queues the email and answers with a receipt.
The cart has a customer and a list of items, with no prices: {"customer_id": 5, "items": [{"product_id": 3, "quantity": 2}, {"product_id": 6, "quantity": 1}]}. The shop takes each price from its own products table, never from the client, who could send any price. The order is saved as 'pending' before the payment, so a declined order stays and can be paid later.
The database picks a new row's id. Ending an INSERT with RETURNING id hands that id back, and db.queryForObject(..., Integer.class, ...) reads it as an int. For example, on another table:
int customerId = db.queryForObject("INSERT INTO customers (name, email) VALUES (?, ?) RETURNING id", Integer.class, "Ion Popa", "ion@example.com");
Your code must answer, in this order:
POST /checkout customer 5: product 3 ×2, product 6 ×1 → 201 {"order_id": 12, "payment_id": "pay_1", "status": "paid", "total": 80}
POST /checkout customer 2: no items → 400 {"error": "The cart is empty"}
POST /checkout customer 4: product 8 ×25 → 402 {"error": "Payment declined", "order_id": 13}
POST /checkout customer 1: product 4 ×3, product 5 ×1 → 201 {"order_id": 14, "payment_id": "pay_2", "status": "paid", "total": 21}
POST /admin/run-jobs → 200 {"ran": 2}
The empty cart saves nothing. The 25 gift cards cost 1250, over the provider's limit of 1000, so order 13 stays 'pending'. No email goes out during a checkout: only when the worker runs do the customers of orders 12 and 14 get their "Your order … is paid" email.
Your task: add POST /checkout. Refuse an empty cart with 400, save the order and its items with the shop's prices, charge the total, answer 402 if the card is declined, and otherwise mark the order paid, queue its email job and answer the receipt with 201.
Assignment
Follow the Coding Tutorial to build the checkout route step by step, reusing what findOrder and payOrder already do.
Hint
Check for an empty cart before any INSERT. The new order's id comes back from the INSERT's RETURNING id, and once the items are saved, findOrder(orderId).get("total") is the amount to charge. Java only compiles checkout once every path through it ends in a return.
Free plan: 30 tutor messages. PRO is unlimited.
