Functional Weave
Code in TypeScript

retail.oss-vat@1.0.1

README.md

4,378 bytes · view raw

# retail.oss-vat

VAT on a sale from a business to a consumer in another EU member state:
distance sales of goods, and telecommunications, broadcasting and electronic
(TBE) services. Since 1 July 2021 these are taxed where the customer is, and
declared through the One Stop Shop (OSS), except for small sellers.

## The rule

- **Same country** as the seller: a domestic sale at that country's rate
  (`domestic`).
- **Seller established in one EU member state, small:** while its total
  cross-border B2C distance sales and TBE services to other member states stay
  at or below EUR 10,000 in the current calendar year, and did in the previous
  one, it charges its own country's VAT (`origin`). The sale that takes this
  year's total over EUR 10,000 is already taxed at destination, which is why
  `euSalesThisYear` includes this supply. A seller may opt to tax at
  destination anyway (`optedIn`); the option binds for two calendar years.
- **Otherwise**, and always for sellers established outside the EU: the
  customer's member state's VAT (`oss`).

VAT is `net x rate`, rounded half up to the minor unit with
`money.apply-rate`. A negative net (a credit note) gives negative VAT.

## Rates are dated data

`data/oss-rules.json` holds each member state's **standard** rate from the
start of OSS on 2021-07-01, with each change since on its effective date, and
the EUR 10,000 threshold (in euro cents) as a dated row too. Rows that say
2021-07-01 are rates that were already in force then, not their enactment
dates. Changes recorded: Luxembourg 17% to 16% for 2023 and back to 17% from
2024; Estonia 20% to 22% from 2024-01-01 and 24% from 2025-07-01; Finland 24%
to 25.5% from 2024-09-01; Slovakia 20% to 23% from 2025-01-01; Romania 19% to
21% from 2025-08-01.

A build installed with `history=current` keeps only the rates in force and
refuses an older date rather than answering it at today's rate.

## Not modelled

- Reduced, super-reduced and parking rates. Books, food, children's clothes
  and much else are taxed at reduced rates that differ by country and product;
  this capability knows only standard rates, so a caller selling reduced-rate
  goods must look the rate up by product.
- Territories outside the EU VAT area (the Canary Islands, whose 7% "VAT"
  row appears in the Commission's database, Åland, Mount Athos and others)
  and the reduced rates of Madeira and the Azores.
- Imports in consignments up to EUR 150 (the Import One Stop Shop, IOSS),
  Northern Ireland sellers (XI), and deemed-supplier rules for marketplaces.
- Converting sales totals into euros: pass the thresholds in EUR, converted
  as the member state of identification requires.

## Sources

- Standard rates: European Commission, Taxes in Europe Database (TEDB),
  queried through its VAT retrieval service
  (https://ec.europa.eu/taxation_customs/tedb/ws/, WSDL
  VatRetrievalService.wsdl) on 2026-09-23 for all 27 member states, from
  2021-07-01 to 2026-09-23. Human-readable entry point:
  https://taxation-customs.ec.europa.eu/taxation/vat/vat-directive/vat-rates_en
  (links to TEDB, https://ec.europa.eu/taxation_customs/tedb/). The database
  returned semi-annual snapshots; the change dates above are the effective
  dates of the national measures, each confirmed by the snapshots either side.
  The latest snapshot the service returned is 2026-07-01 for most states,
  2026-01-01 for Cyprus and Portugal and 2025-01-01 for Greece; their rates are
  assumed unchanged since, and should be re-checked before publishing.
- Threshold: Council Directive 2006/112/EC (the VAT Directive), article 59c,
  as amended by Council Directive (EU) 2017/2455; the application date was
  moved to 1 July 2021 by Council Decision (EU) 2020/1109. The Commission's
  "Guide to the VAT One Stop Shop" (section on the EUR 10 000 threshold)
  states the rule used here: one threshold for TBE services and intra-EU
  distance sales together, net of VAT, current and preceding calendar year,
  for suppliers established in only one member state:
  https://vat-one-stop-shop.ec.europa.eu/system/files/2021-07/OSS_guidelines_en.pdf
  and the list of legal acts at
  https://vat-one-stop-shop.ec.europa.eu/vat-e-commerce/eu-legislation_en

1.0.1 fixes Python accepting a trailing newline in sellerCountry and customerCountry, and a trailing newline or non-ASCII digits in onDate; adds tests.