---
id: CAP-COM-CAT-004
title: Calculate tax from buyer geography
feature: FEAT-COM-CAT-01
product_area: commerce
module: catalog
revision: 1
status: draft
product_intent: proposed
implementation_evidence: inconsistent
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
intent_sources:
  - >-
    services-commerce:docs/milestones/plans/COMM-realignment-2026-10-22.md
    (proposed, awaiting Andrew)
purpose: >-
  Commerce determines line tax from the configured provider and the buyer’s
  applicable location.
outcomes:
  - >-
    Each taxable line is taxed by the most specific zone the purchaser’s address
    falls in.
  - >-
    A purchase that requires an address to be taxed is prevented from being
    placed without one.
concepts:
  - CON-COM-CAT-001
actors:
  - ACT-COM-001
surfaces:
  - api
permissions:
  - key: commerce.tax-zones.read
    where_checked: api
    scope: tenant
  - key: commerce.tax-zones.create
    where_checked: api
    scope: tenant
  - key: commerce.tax-zones.update
    where_checked: api
    scope: tenant
  - key: commerce.tax-zones.delete
    where_checked: api
    scope: tenant
  - key: commerce.tax-zones.bulk
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-COM-CAT-004-01
    proved_by: [ATS-COM-CAT-004-01, TST-COM-CAT-032]
    statement: >-
      A tax zone uses an assigned two-letter country code, optionally narrowed
      by region and postal prefix; invalid country codes are refused on the
      country field.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers
      400 to a country code that is not two upper-case letters, naming the field
    origin: none
  - id: BR-CAP-COM-CAT-004-02
    proved_by: [ATS-COM-CAT-004-01, TST-COM-CAT-032]
    statement: >-
      A tax rate requires a name, an integer number of basis points within the
      supported ceiling, and dates whose start does not exceed their end.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers
      400 to a rate past the ceiling and to a rate without a name, naming the
      field
    origin: none
  - id: BR-CAP-COM-CAT-004-03
    proved_by: [ATS-COM-CAT-004-02, TST-COM-CAT-042]
    statement: >-
      Tax edits reject whitespace-only optional values and clear a field such as
      postal prefix only when an explicit null is supplied.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses
      a whitespace-only postal_code_prefix on a replacement 400 naming the
      field, instead of clearing H2X
    origin: none
  - id: BR-CAP-COM-CAT-004-04
    proved_by: [ATS-COM-CAT-004-03, TST-COM-CAT-042, TST-COM-CAT-089]
    statement: >-
      A rate with no product-type selection covers all types; a selected list
      limits coverage to those types and cannot exceed one hundred entries.
    kind: calculation
    enforced_at:
      - service
      - api
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#applies
      to every product type when it names none, and to the named ones alone
      otherwise
    origin: none
  - id: BR-CAP-COM-CAT-004-05
    proved_by: [ATS-COM-CAT-004-04, ATS-COM-CAT-004-07, ATS-COM-CAT-013-15, TST-COM-CAT-089]
    statement: >-
      Tax determination selects the most specific matching active zone,
      preferring postal prefix over region over country and retaining list order
      for equal specificity.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores
      a prefix over a region over a country alone
    origin: none
  - id: BR-CAP-COM-CAT-004-06
    proved_by: [ATS-COM-CAT-004-04, ATS-COM-CAT-004-07, ATS-COM-CAT-013-15, TST-COM-CAT-089]
    statement: >-
      Line tax sums separately calculated rates, each rounded to a whole minor
      currency unit with exact halves moving away from zero.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#sums
      every applicable rate per line, each rounded on its own, and names each
      component
    origin: COMM-M1 D5 (docs/milestones/COMM-M1-set-up-what-you-sell.md)
  - id: BR-CAP-COM-CAT-004-07
    proved_by: [ATS-COM-CAT-004-05, ATS-COM-CAT-004-07, ATS-COM-CAT-013-15, TST-COM-CAT-088]
    statement: >-
      Potentially taxable checkout lines require a tax address; an address-free
      display quote instead reports zero tax and a warning.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-table-tax.spec.js#refuses
      a line a zone’s rate for every product type could tax, naming the line and
      its product
    origin: none
  - id: BR-CAP-COM-CAT-004-08
    proved_by: []
    statement: >-
      Commerce uses workspace tax zones unless configured to delegate tax to
      Accounting, and rejects quotes configured with an unknown provider.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/commerce-tax-port.spec.js#is
      zone-table when the variable is unset or blank
    origin: none
  - id: BR-CAP-COM-CAT-004-09
    proved_by: [ATS-COM-CAT-004-08]
    statement: >-
      Replacing a tax zone replaces its rates at the read version, and removing
      the zone removes its rates.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: false
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#deletes
      a zone with its rates, after which it reads 404; deleting it again is 404
    origin: none
  - id: BR-CAP-COM-CAT-004-10
    proved_by: [ATS-COM-CAT-004-06, TST-COM-CAT-028]
    statement: >-
      Tax-zone inspection requires read permission, while creation, replacement
      and removal require administrator write permissions.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#lists
      and reads the tax zones
    origin: none
scenarios:
  - id: SC-CAP-COM-CAT-004-01
    given: Canada and a more specific Quebec H2X zone are configured
    when: Calculate for a Quebec address beginning H2X
    then: >-
      The prefix-specific zone supplies the tax rather than the country-only
      zone
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#matches
      the most specific zone the address satisfies
depends_on: []
related:
  - CAP-COM-CAT-013
open_questions:
  - OQ-COM-CAT-002
evidence:
  - >-
    Pinned service inputs: modules/configuration/aws/api/tax-zones.routes.ts and
    .schemas.ts; services/persistence/tax-zones.ts; db/tax-zones.table.ts,
    tax-rates.table.ts; tests (tax zones e2e, persistence, table, blank values,
    ranges, country codes); modules/pricing/services/tax/** and
    tests/unit/tax/**
  - >-
    Pinned console inputs: no Commerce tax zone screen exists; the console’s tax
    zone screens are Accounting’s (lib/accounting-config/tax.ts)
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-CRY-001, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PRV-001, TC-PRV-002]
exercised_by: [FAS-COM-CAT-01-01, FAS-COM-CAT-01-04]
suites: [TST-COM-CAT-028, TST-COM-CAT-032, TST-COM-CAT-042, TST-COM-CAT-088, TST-COM-CAT-089]
---

# Calculate tax from buyer geography

## Purpose

Commerce determines line tax from the configured provider and the buyer’s applicable location.

## How It Works

An administrator prepares tax zones and their effective rates through the API. During calculation Commerce chooses the matching location and applicable product-type rates. The result retains component amounts so staff can understand the line’s tax. Checkout and display calculation treat absent address information differently. An invalid rate interval or country selection prevents saving the configuration. Staff currently edit Accounting tax configuration in the console, which does not make it the active Commerce provider. The product should choose one coherent ownership and configuration path before designing tax administration.

## Relationships

The calculate and explain a buyer’s item price capability supplies related configuration or assessment used in this work.
