Snapshotting Tax at Checkout

Orders Must Not Change

Congratulations on making it to the final lesson of this course 🎉 You’ve built a full tax configuration system, wired it into carts, and you can now see cart totals change as the tax country changes. That’s a huge backend milestone.

Previously, in “Applying Cart Taxes”, you made carts tax-aware by storing tax_country, resolving a tax_rate_bps (configured or default), and computing totals every time a cart is fetched. That was perfect for a dynamic cart. But orders aren’t dynamic—orders are receipts. This lesson is about making that receipt immutable and trustworthy.

Tax rates change. Sometimes frequently. If your system recalculates tax for old orders using “today’s” rate, you’ll corrupt your financial history.

The key behavior we want is:

  • Updating a tax rate affects future carts and future checkouts.
  • Updating a tax rate must not change existing orders.

That’s what “snapshotting tax at checkout” means: at the moment checkout happens, we store tax_country, tax_rate_bps, tax_cents, and total_cents on the order. After that, reads (listOrders, getOrderById) must simply return those stored values—no recomputation.

All of the work in this lesson lives in src/lib/repositories/ordersRepo.ts.

The Order Mapping Must Include Snapshot Fields

Before we even talk checkout, the simplest way to see “snapshotting” is in the order mapping: if a field exists in the DB row and we want it to be part of the domain order object, mapOrderRow(...) must include it.

This function is the foundation for both listing and fetching orders, because both functions map rows through it.

TypeScript
// src/lib/repositories/ordersRepo.ts
function mapOrderRow(r: OrderRow): Order {
  return {
    id: r.id,
    cart_id: r.cart_id,
    status: ["pending", "paid", "shipped", "cancelled"].includes(r.status)
      ? (r.status as Order["status"])
      : "pending",
    subtotal_cents: r.subtotal_cents,
    tax_cents: r.tax_cents,
    total_cents: r.total_cents,
    currency: r.currency === "USD" ? "USD" : "USD",
    tax_country: r.tax_country ?? null,
    tax_rate_bps: r.tax_rate_bps,
    created_at: r.created_at,
    updated_at: r.updated_at,
  };
}
  • tax_country and tax_rate_bps are the “snapshot metadata” that explain why an order’s tax looks the way it does. Without them, the order would have numbers but no context.
  • tax_cents and total_cents are mapped directly from the row and should be treated as final values once stored.
  • This mapping function is intentionally “dumb”: it doesn’t compute totals or call out to other tables. That’s a critical theme of this lesson—reads should not recompute.
Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal