Skip to main content
Episode 12 of 24Storefront45 min

Build checkout and save a pending order

Add a checkout page with address, shipping options and a tax placeholder, then save a safe pending order whose prices the server calculates itself.

You will have: A working checkout that turns a cart into a saved pending order with an order number, with every price calculated on the server and no payment taken yet.

Before you start

  • Episodes 8 to 11 are done: products, images, cart and customer accounts work on your computer
  • Local Postgres is running and the shop_local database opens without errors
  • Have rough shipping prices in mind (flat rate, free above a certain amount). Placeholders are fine; you can change them later
  • Know whether you will allow guest checkout (buying without an account). Most stores do

Make it yours

Change the example (fashion store) to your own. Your answers are saved in this browser and used in every episode.

Other series keywords (4)

Your prompt

Paste into: Your AI agent (Claude Code or Codex) opened in your project folder (the folder you created in Episode 4).
Context: this project is the online store "Thread & Needle", a fashion e-commerce store that sells women's and men's clothing, shoes and accessories to customers mainly in United States and charges in USD. It uses Next.js (App Router), TypeScript, Tailwind CSS, PostgreSQL with Drizzle ORM (local database shop_local), Zod and Auth.js. Product pages, images, the cart and customer accounts already exist from earlier episodes.

Goal: build the checkout and save a pending order. Do NOT take payment yet. The payment gateway (Stripe) is connected in the next episode.

Plan first: read the project, then show me your plan (files to add or change, new database tables) and wait for my OK before changing anything. Explain any technical word in a few plain words.

Tasks, after I approve:
1. Database (Drizzle migration, applied only to shop_local): add orders, order_items and, for signed-in customers, saved addresses. Store all money as whole numbers in the smallest currency unit (for example cents), never as decimals. An order holds: an order number, status (starts as pending), customer email, optional user id, currency, subtotal, shipping, tax, total, a copy of the shipping address, a copy of the chosen shipping method, and created time. Each order item holds a copy of the product name, variant, unit price, quantity and image at the time of purchase, so later edits to the catalogue never change old orders.
2. Checkout page: contact email, shipping address (use the fields and postcode format that fit United States), shipping option, and an order summary. Signed-in customers get their saved address prefilled. Guests can check out without an account if I confirm it.
3. Shipping options: keep two or three options in one config file, with placeholder prices in USD and an optional free-shipping threshold. Mark them clearly as placeholders I must confirm.
4. Tax placeholder: one function that returns tax for an order. For now it returns zero and the page says tax is not yet calculated. Do not claim any tax compliance; tell me this is a decision for me and a tax adviser.
5. Server-side pricing: write one function that rebuilds the cart from the database and computes every total. It must ignore any price, total or shipping cost sent from the browser. It must reject unpublished products, missing variants, quantities below 1 or above a sensible limit, and quantities that exceed stock.
6. Place-order action: validate all input with Zod, apply a simple rate limit, recompute totals, then create the order and its items in one database transaction. Generate a readable order number from a short store prefix plus a database sequence. Redirect to a "pending order" page. Guests may view their order only with a long random access token; store only its hash.

Security rules: never trust prices or ids from the browser; check that a signed-in customer can only read their own orders; do not log full addresses; never print, log or commit any secret; only use names in .env.example.

Verify yourself and show me the results: write Vitest tests for the pricing function (tampered price, negative quantity, unpublished product, out-of-stock item, free-shipping threshold), then run the tests, the type check and the linter. Start the site and place one test order from the cart, then show me the order rows in shop_local.

Commit to a new branch named feature/checkout-orders with a clear message.

Report back in this format: 1) What was done. 2) What I should check in the browser. 3) Anything that needs my decision (shipping prices, tax, guest checkout).

Review the prompt, then copy it. Never paste real passwords or API keys into a chat.

How to check it worked

  • Adding items and opening the checkout shows the same items and prices as the cart
  • Choosing a different shipping option changes the total, and a cart above your free-shipping amount shows free shipping
  • Submitting a valid order lands on a pending order page showing an order number
  • The order and its items appear in the shop_local database with status pending
  • Changing a product price in the database after ordering does not change the saved order
  • The agent shows passing Vitest results, including a test that a tampered price is ignored

Sample result

Example: fashion store

Example checkout for the fashion store. The totals shown here are placeholders the server recalculates.

If something goes wrong

The checkout total does not match the cart total.
Tell the agent: "The cart and checkout totals differ. Make both pages use the same pricing function on the server, then add a test that compares them."
Placing an order fails with a database error such as a missing table or column.
The migration probably did not run. Ask: "Show me which migrations have been applied to shop_local and apply the missing one. Do not touch any other database."
The address form asks for fields that do not fit your country (for example a state or a different postcode style).
Reply with your country's address format and ask the agent to adjust the form and the Zod rules, then add one test with a valid and one with an invalid address.
A guest cannot open their pending order page again.
That is by design: the link with the secret token is the only way in. Ask the agent to show the guest the link once and to email it in Episode 14. Do not make order numbers openly viewable.

More in Storefront

This is part of the series Build a secure, no-cost e-commerce website with AI agents. See all 24 episodes, the tools used and answers to common questions on the series page.

We use optional cookies for analytics and referral tracking. Learn more about our privacy policy