Applying Country Based Taxes

Applying Country-Based Taxes

Welcome back! 👋 Now that we have a dedicated Tax Rates API, it is time to actually apply those tax settings to a cart. This is the moment where tax stops being just configuration data and starts affecting what a customer sees during shopping.

In this lesson, we will connect a cart’s selected country to its computed totals. That means we will validate incoming tax-country updates, store the selected country on the cart, look up the matching tax rate when loading the cart, and return updated totals so the UI can immediately reflect the new subtotal, tax, and grand total. This makes the cart itself tax-aware long before checkout happens.

Previously, we finished polishing the Orders API: Checkout Workflow and Order State Transitions and then began building the Tax API. That gave us a central place to manage country-based tax rates. Now we are wiring that configuration into the cart flow, so a cart can resolve the correct tax rate and show accurate totals before an order is ever created.

Why Tax Must Be Applied at the Cart Level

A cart is where customers see pricing take shape. They add items, change quantities, and decide whether they want to continue toward checkout. If tax is only considered at the very end, the customer does not get a realistic view of what they are about to pay.

That is why this lesson focuses on making the cart totals depend on a selected tax_country. Once that country is stored on the cart, the backend can look up the corresponding rate from tax_rates, compute the tax from the cart subtotal, and return totals that already include the correct tax amount.

Just as importantly, this logic belongs on the server. The UI should not guess or calculate tax on its own. Instead, the backend should derive totals from the cart items plus the resolved tax rate, so every client sees the same numbers and the calculations remain consistent.

Validating the Tax Country Payload

The first piece of this feature lives in src/lib/services/cartsService.ts. This file already contains service-level validation and business rules for cart operations, so it is the right place to validate the incoming tax-country payload as well.

In this lesson, the client is expected to send JSON shaped like { "country_code": "US" }. The service validates that shape, normalizes the value, and returns a clean validation result that the route can turn into a friendly 400 response.

export function validateTaxCountryInput(
  input: unknown,
): { ok: true; value: string } | { ok: false; message: string } {
  const payload = input as { country_code?: unknown };
  if (typeof payload.country_code !== "string") {
    return { ok: false, message: "country_code must be a string" };
  }
  const normalized = payload.country_code.trim().toUpperCase();
  if (!/^[A-Z]{2}$/.test(normalized)) {
    return { ok: false, message: "country_code must match /^[A-Z]{2}$/" };
  }
  return { ok: true, value: normalized };
}
  • This function expects an object-shaped payload and specifically looks for country_code. That matches the API contract for this route, which is important because it keeps the backend strict and predictable instead of silently accepting many different request shapes.

  • The validation first ensures country_code is actually a string. That prevents invalid values like numbers, booleans, arrays, or missing fields from moving further into the service layer and causing confusing behavior later.

  • The trim().toUpperCase() normalization step makes the API more forgiving without making it loose. A value like " us " will still become "US", which is useful because it lets the server accept minor formatting mistakes while still storing a clean canonical value.

  • The regex /^[A-Z]{2}$/ enforces exactly two uppercase letters. That matches the rest of the tax-rate design in this course, where country codes are stored and looked up in a normalized two-letter form like US, CA, or GB.

  • Instead of throwing an exception, the function returns a structured { ok: false, message } result on failure. That pattern fits the rest of the service layer and gives the route a clear way to send a friendly 400 VALIDATION_ERROR response back to the client.

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