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.
tax_countryis explicitly nullable, which matches real usage: a user might not have provided a country yet, but the cart still needs to work.- Keeping
tax_countryon 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.
- The first guard (
if (!countryCode)) ensures carts without a selected country still get consistent totals by usingDEFAULT_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_ratesrow exists, we still fall back toDEFAULT_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.
