Applying Cart Taxes

The Big Picture: How Cart Taxes Work in This Codebase

Welcome back! 👋 In the last lesson, you built a Tax Rates Configuration API so the backend can store and manage tax rates per country. Now we’re going to apply those rates where they matter: inside the shopping cart totals.

In this lesson, you’ll make carts “tax-aware” by storing a cart’s tax_country, resolving an appropriate tax_rate_bps (configured rate or a default), and recalculating totals every time the cart is fetched. You’ll also add a focused PATCH endpoint that lets clients update the cart’s tax country and immediately see refreshed totals.

At a high level, the flow looks like this:

A cart stores an optional tax_country (like "US"). When we fetch the cart, the repository computes subtotal_cents from the cart item snapshot prices, resolves a tax_rate_bps using the stored tax_country (falling back to DEFAULT_TAX_RATE_BPS when needed), and calculates tax_cents + total_cents. Separately, a small route handler lets clients set the cart’s tax_country via PATCH /api/carts/:id/tax, and the service ensures the cart exists and is still open before saving.

Storing Tax Country on the Cart

This project treats tax_country as cart-level configuration: it’s stored on the carts table and used later when totals are computed. You can see this reflected in the cart row type and in how the cart domain object is built.

The repository definitions live in src/lib/repositories/cartsRepo.ts.

// src/lib/repositories/cartsRepo.ts
export interface CartRow {
  id: string;
  status: string;
  tax_country: string | null;
  created_at: string;
  updated_at: string;
}
  • tax_country is explicitly nullable, which matches real usage: a user might not have provided a country yet, but the cart still needs to work.
  • Keeping tax_country on the cart means the totals computation can be deterministic: it always uses the cart’s stored value rather than “guessing” from headers, IP, or frontend defaults.
  • This structure also makes the “set tax country” endpoint simple: it updates a single column and then returns a refreshed cart view.

Resolving the Tax Rate: Configured or Default

The cart totals code must always use a stable tax rate. The key rule in this project is:

Totals should not guess the rate — they should either use a configured tax rate or a default.

That logic is centralized in resolveTaxRateBps(...), which lives in src/lib/repositories/cartsRepo.ts.

// src/lib/repositories/cartsRepo.ts
import { computeTaxCents, DEFAULT_TAX_RATE_BPS } from "@/lib/money/tax";

async function resolveTaxRateBps(countryCode: string | null): Promise<number> {
  if (!countryCode) return DEFAULT_TAX_RATE_BPS;
  const rows = await query<{ rate_bps: number }>(
    "SELECT rate_bps FROM tax_rates WHERE country_code = $1",
    [countryCode],
  );
  return rows[0]?.rate_bps ?? DEFAULT_TAX_RATE_BPS;
}
  • The first guard (if (!countryCode)) ensures carts without a selected country still get consistent totals by using DEFAULT_TAX_RATE_BPS.
  • The query is parameterized ($1) to keep it safe and consistent with the rest of the repo layer.
  • If the country code is set but no tax_rates row exists, we still fall back to DEFAULT_TAX_RATE_BPS. This keeps totals stable even when configuration is incomplete.
  • This function is intentionally “boring”: it does not validate country codes or throw 404s. Validation happens in services/routes; the repo just resolves “best available” for totals.
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