Skip to content

feat(plugins): bundled era-based tax rate tables (CH, EU, UK, US) + update-tax-rates skill - #727

Open
pozylon wants to merge 7 commits into
v4.8.xfrom
feat/swiss-tax-rates-json
Open

feat(plugins): bundled era-based tax rate tables (CH, EU, UK, US) + update-tax-rates skill#727
pozylon wants to merge 7 commits into
v4.8.xfrom
feat/swiss-tax-rates-json

Conversation

@pozylon

@pozylon pozylon commented Aug 20, 2026

Copy link
Copy Markdown
Member

What

Moves tax rates out of code into bundled, era-based JSON tables — versioned, reviewable, and historically complete — and adds aligned tax plugins for the EU, the UK and the US next to the existing Swiss pair, plus an update-tax-rates maintainer skill that verifies/updates all bundled tables against official sources.

Targets v4.8.x. Rates are deliberately not fetched at runtime: tax rates are legally binding, so every change must be a reviewable diff, and the applicable rate follows the supply date — which requires the full era history, not "the current rate".

Commits (atomic per plugin)

  1. sharederaRates (compile/sort {validFrom, rate} eras, resolve by reference date, clamp before first era), applyTaxRateToTaxableRows (the common adapter tax math: gross rows get tax extracted via offset+tax rows, net rows additive), resolveDeliveryLocation (delivery-address → billing-address → order-country, incl. regionCode; isDeliveryAddressInCountry now delegates).
  2. CHSwissTaxCategories reads ch-tax-rates.json (emitted to lib/ by tsc via ESM JSON import attributes). Public API unchanged. Behavior fix: 2011–2017 orders were priced at the 2018–2023 rates (7.7/2.5/3.7%); the table resolves the legally correct 8.0/2.5/3.8%. History back to 2001, earlier dates clamp.
  3. EUeu-tax-rates.json with per-country era histories for all 27 member states (verified against the EC's published rates; incl. DE 2020 temporary cut, LU 2023, CZ/EE 2024, SK/RO/EE 2025, FI/LT 2026). Destination country picks the table; eu-tax-category:<name> product tag or provider config picks the category, defaulting to standard (unknown categories fall back to standard, never zero). Greece resolves as GR and EL. OSS/reverse-charge out of scope.
  4. UK — standard (17.5 → 15 → 17.5 → 20 incl. the 2008–2010 crisis cut), reduced (8 → 5), zero; UK VAT area = GB + Isle of Man; verified against gov.uk / Commons Library history.
  5. US — statewide base sales-tax rates for 50 states + DC (era history for LA 2025, NM 2022/23, SD temporary cut, DC's enacted 2026-10-01 increase). State from the delivery address regionCode; unknown state applies no tax (logged) instead of a made-up fallback; us-tax-category:exempt tag / provider config opt-outs. Documented approximation: no county/city add-ons, category taxability, nexus or sourcing — full US compliance needs a tax service.
  6. skill.claude/skills/update-tax-rates: per-region official sources (ESTV/Fedlex, EC TEDB, gov.uk, state DORs), cross-check rule, append-only eras, never guess, tests updated with each change, human review before commit.

Verification

  • @unchainedshop/plugins: 136/136 unit tests pass, including new suites for era histories, exact effective-date boundary switches, tag/provider category resolution, and per-file JSON-consistency checks (ascending eras, plausible rates, source/verification metadata)
  • tsc -b clean; all four JSON tables emitted to lib/ and loadable from the compiled ESM output
  • eslint + prettier clean

All rate data was researched with per-country/state source citations; figures that could not be verified against an official source were excluded rather than guessed. Effective dates for long-standing US rates use the source table's as-of anchor date (documented in the research; single-era states resolve identically for all past dates via era clamping).

Related: #726

  1. docs — one page per new adapter following the Swiss tax doc template, pricing-plugin index extended, Swiss pages updated for the era-based tables (incl. the corrected 2011–2017 / 2001–2010 rate columns), and a prominent statewide-approximation warning on the US pages.

- eraRates: compile/sort bundled {validFrom, rate} eras and resolve the rate
  applicable at a reference date (clamping to the earliest known era)
- applyTaxRateToTaxableRows: the common tax math of all regional adapters
  (gross rows get tax extracted via offset+tax rows, net rows additive)
- resolveDeliveryLocation: delivery-address > billing-address > order-country
  resolution incl. regionCode; isDeliveryAddressInCountry delegates to it
…able

Rates move from hardcoded conditionals into ch-tax-rates.json (bundled and
emitted to lib/ by tsc), with the full era history back to 2001 and ESTV
source/verification metadata. The public SwissTaxCategories API is unchanged;
both Swiss adapters now share applyTaxRateToTaxableRows.

Behavior fix: 2011-2017 orders were priced with the 2018-2023 rates
(7.7%/2.5%/3.7%); the era table now resolves the legally correct
8.0%/2.5%/3.8% for those dates.
…ry era tables

eu-tax-rates.json bundles standard/reduced/reduced2/super_reduced/parking
era histories for all 27 member states, verified against the European
Commission's published rates (EC annex history + recent changes: DE 2020
temporary cut, LU 2023, CZ/EE 2024, SK/RO/EE 2025, FI/LT 2026).

The product-eu-tax/delivery-eu-tax adapters mirror the Swiss pair: the
delivery country picks the table, a product's eu-tax-category:<name> tag or
the provider's eu-tax-category configuration picks the category, defaulting
to the standard rate (unknown categories fall back to standard, never to
zero). Greece resolves under both GR (ISO) and EL (EU convention).
Cross-border regimes (OSS, reverse charge) stay out of scope.
uk-tax-rates.json bundles the standard (17.5 → 15 → 17.5 → 20 incl. the
2008-2010 crisis cut), reduced (8 → 5) and zero categories, verified against
gov.uk / the House of Commons Library rate history. The
product-uk-tax/delivery-uk-tax adapters mirror the Swiss pair for the UK VAT
area (GB + Isle of Man); products pick categories via uk-tax-category:<name>
tags, providers via a uk-tax-category configuration entry, default standard.
…rates

us-tax-rates.json bundles the statewide base rate for the 50 states + DC
(incl. mandatory statewide local components where quoted that way, era
history for recent changes: LA 2025, NM 2022/23, SD temporary cut, DC's
enacted 2026-10-01 increase). Documented approximation: no county/city
add-ons, category taxability, nexus or sourcing rules — full US compliance
needs a tax service.

The product-us-sales-tax/delivery-us-sales-tax adapters resolve the state
from the delivery address regionCode; unknown states apply no tax (logged)
rather than a made-up fallback. Products opt out via us-tax-category:exempt
tags, delivery providers via a us-tax-category=exempt configuration entry.
Net price rows get tax added on top (US convention), gross rows extracted.
Maintainer workflow for all four bundled tables (CH/EU/UK/US): fetch the
official sources, cross-check every change against a second source,
append-only era updates, never guess a rate, update tests, and present the
diff with quoted sources for human review — rates change as reviewed,
versioned diffs instead of runtime fetches.
@pozylon
pozylon changed the base branch from master to v4.8.x August 20, 2026 09:59
@pozylon
pozylon force-pushed the feat/swiss-tax-rates-json branch from 3f6d67b to 5ecd2ad Compare August 20, 2026 10:01
One page per adapter following the Swiss tax doc template (installation,
category/tag/provider configuration, net-vs-gross handling, adapter details),
a prominent statewide-approximation warning for the US pair, the pricing
plugin index extended, and the Swiss pages updated for the era-based JSON
tables incl. the corrected 2011-2017 and 2001-2010 rate columns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant