Home › Learn › JavaScript › Async, errors, and classes

Project: checkout

Lesson 40 of 40. Assemble the parts you built along the way into a checkout that turns an order into a receipt.

Every function this project needs already exists: you wrote them, one or two per module, and they load before your code as if they were the top of the same file. The workspace lists them and says which version it is using (a lesson you skipped contributes the reference version until you do it). TAX_RATE, shippingCost, pad, sum, cartTotal, money, addDays, makeDiscount, fetchStock, validateOrder, and Receipt are all in scope.

Work in steps. Parse and validate first, then the stock check, then the receipt lines one at a time, running after each. When a check fails, its message shows what it expected. If a part misbehaves, fix it in its own lesson; the project picks up the new version on the next run.

Your task

Write async function checkout(orderText). The text is JSON like { "customer": "Ada", "date": "2026-03-01", "discount": 0.1, "items": [{ "name": "keyboard", "price": 89, "qty": 1 }] } (discount is optional). Parse it, pass it through validateOrder, then for every item await fetchStock(item.name) and throw an Error not enough <name> in stock when the quantity is more than the stock.

Build a Receipt for the customer with one line per item labeled <name> x<qty> and the amount price * qty; then, when there is a discount, a Discount line with a negative amount equal to what makeDiscount(discount) takes off the cartTotal; then Shipping from shippingCost of the discounted subtotal; then Tax, the discounted subtotal times TAX_RATE rounded to two decimals.

Return { customer, lines, total, delivery }: lines is each receipt line as pad(label, 20) + money(amount) followed by a Total line built the same way from the receipt's total; total is that number; delivery is addDays(date, 3).

This is the final project. It is built from the 11 parts you made along the way; the workspace lists them and which version it is using.

Hint 1

for (const item of order.items) { const stock = await fetchStock(item.name); if (item.qty > stock) throw new Error(`not enough ${item.name} in stock`); }

Hint 2

const subtotal = cartTotal(order.items); let due = subtotal; if (order.discount) { due = makeDiscount(order.discount)(subtotal); receipt.add("Discount", -(subtotal - due)); } receipt.add("Shipping", shippingCost(due)); receipt.add("Tax", Math.round(due * TAX_RATE * 100) / 100); The Discount amount may need rounding: Math.round(x * 100) / 100.

Show the solution
async function checkout(orderText) {
  const order = validateOrder(JSON.parse(orderText));
  for (const item of order.items) {
    const stock = await fetchStock(item.name);
    if (item.qty > stock) throw new Error(`not enough ${item.name} in stock`);
  }
  const receipt = new Receipt(order.customer);
  for (const item of order.items) receipt.add(`${item.name} x${item.qty}`, Math.round(item.price * item.qty * 100) / 100);
  const subtotal = cartTotal(order.items);
  let due = subtotal;
  if (order.discount) {
    due = makeDiscount(order.discount)(subtotal);
    receipt.add("Discount", -Math.round((subtotal - due) * 100) / 100);
  }
  receipt.add("Shipping", shippingCost(due));
  receipt.add("Tax", Math.round(due * TAX_RATE * 100) / 100);
  const lines = receipt.lines.map((l) => pad(l.label, 20) + money(l.amount));
  lines.push(pad("Total", 20) + money(receipt.total));
  return { customer: order.customer, lines, total: receipt.total, delivery: addDays(order.date, 3) };
}
Your partsChecking your parts…

    They load before your code, as if they were the top of the same file.

      Runs in an isolated web worker in this tab; nothing is sent anywhere.