Skip to content

Source

This page is generated from docs/tax-semantics.md at the pinned revision 5ab878035219. Edit it there, not here.

Tax semantics — what the validator does not catch ​

Not tax advice. This document lists the VAT rules einvoice-commerce applies — one scenario per row, each with its German/EU legal source and the EN 16931 business rules it triggers — so that you and your tax advisor can verify them against your situation. Every row is executable: packages/einvoice-commerce/src/tax-rules.ts (decideVatCategory()), one branch and one test per row. Where a rule cannot be decided generically, the software refuses and says why (see "What the validator does not catch"). Scope: Germany as seller country. All identifiers are synthetic.

Why this document exists ​

L1–L3 validators (AGENTS.md §8) check that a VAT category is structurally well-formed — the right fields present, the right amounts computed. They cannot check that the category is the correct one for a given seller/buyer/supply combination — that is a judgment call encoded in einvoice-commerce (TaxContext → TaxDecision), and it is the one layer conformance tooling cannot grade (AGENTS.md §8.5). This document exists to make that judgment explicit and reviewable, one scenario at a time, before a single line of einvoice-commerce is written.

Scope: Germany only, matching v0.1. All identifiers below are synthetic (AGENTS.md §5.2) — DE 123456789 follows the convention already used by KoSIT's own XRechnung test suite (vendored at packages/einvoice-conformance/src/__fixtures__/kosit-testsuite-01.01a-INVOICE_ubl.xml).

Scenario table ​

One row = one scenario. BR-* references are verified against the vendored EN 16931 CII Schematron (artifacts/cii-d16b/schematron/EN16931-CII-validation-preprocessed.sch, EUPL-1.2, see sources.md) — not recalled from memory. VATEX-* codes are verified against the Peppol BIS 3.0 codelist documentation, which cross-references the exact BR-*-10 rule each code supports.

#ScenarioSellerBuyerSupplyRegimeCategory (BT-118)Rate (BT-119)Exemption (BT-120/121)Key mandatory fieldsApplicable BR-*Norm source
1DE → DE B2B, standard rateDE, DE 123456789DE B2B, DE 987654321Consulting servicesDomesticS19%—Seller VAT-ID (BT-31)BR-S-01, BR-S-02, BR-S-08, BR-S-09UStG §12 Abs. 1
2DE → DE B2B, reduced rateDE, DE 123456789DE B2B, DE 987654321Books (Anlage 2 UStG)DomesticS7%—Seller VAT-ID (BT-31)Same BR-S-* as #1 — EN 16931 has no separate category for a reduced rate, only a lower BT-119 under SUStG §12 Abs. 2 Nr. 1 + Anlage 2
3DE → FR B2B, VAT-ID givenDE, DE 123456789FR B2B, FR 12345678901Goods, dispatched to FRIntra-EUK0%BT-121 VATEX-EU-IC ("Intra-Community supply")Seller VAT-ID (BT-31) and Buyer VAT-ID (BT-48) (BR-IC-02); Actual delivery date (BT-72) or Invoicing period (BG-14) (BR-IC-11); Deliver-to country code (BT-80) (BR-IC-12)Seller VAT-ID (BT-31) and Buyer VAT-ID (BT-48) — BR-IC-02 requires both and is flag="fatal"; Actual delivery date (BT-72) or Invoicing period (BG-14) (BR-IC-11); Deliver-to country code (BT-80) (BR-IC-12)UStG §4 Nr. 1b + §6a (intra-Community supply); Art. 138 VAT Directive
4DE → CH (non-EU)DE, DE 123456789CH, CHE-123.456.789 MWSTGoods, exported to CHExportG0%BT-121 VATEX-EU-G ("Export outside the EU")Deliver-to country (BT-80) outside the EU — a delivery inside the EU is refused, whoever the buyer is (§6 Abs. 1 UStG)Seller VAT-ID (BT-31) or tax representative VAT-ID (BT-63) (BR-G-02); exemption reason BT-120 or BT-121 "Export outside the EU" (BR-G-10). Buyer VAT-ID is not required — a third-country buyer usually has noneBR-G-01, -02, -05 (analogue), -08, -09, -10
5DE → DE B2B, reverse charge service (domestic only — a cross-border reverse-charge service is row 12, a different override)DE, DE 123456789DE B2B, DE 987654321Construction/subcontractor service (§13b UStG)Domestic, reverse chargeAE0%BT-121 VATEX-EU-AE ("Reverse charge") — mandatory, BR-AE-10Seller VAT-ID (BT-31, BR-AE-02)Seller VAT-ID (BT-31/BT-32/BT-63) and Buyer VAT-ID (BT-48) and/or Buyer legal registration ID (BT-47) — BR-AE-02 requires both sides; §14a Abs. 5 UStG additionally requires the text "Steuerschuldnerschaft des Leistungsempfängers"UStG §13b
6DE domestic, exempt supplyDE, DE 123456789DE, B2B or B2Ce.g. medical/educational service under §4 UStGDomestic, exemptE0%BT-120 free text naming the §4 UStG exemption (no single universal VATEX-* code — the exact EU directive article varies by exemption type; ⚠️ which one applies depends on the supply) — mandatory, BR-E-10—Seller VAT-ID (BT-31/BT-32/BT-63) (BR-E-02); BT-120 text or BT-121 code (BR-E-10 — either one suffices)BR-E-01, -02, -08, -09, -10
7DE → EU B2C, distance sale (OSS)DE, DE 123456789Another EU state, B2C (no VAT-ID)Goods, distance saleOSS one-stop-shopSbuyer's country rate, not Germany's——Same BR-S-* as #1Art. 33 VAT Directive; ⚠️ outside the DE B2B/B2G mandate this project targets, but occurs in mixed merchant catalogs — rate is supplied by the caller (ossRateOverride), not derived automatically. v0.1 scope: the engine only ever accepts two declared facts — the merchant is OSS-registered (ossRegistered, a standing seller-level fact) and the destination-country rate for this order (ossRateOverride, a per-order fact) — and refuses without both. It covers goods only: an OSS sale of services is refused, since a service to a consumer is taxed in Germany as a rule (§3a Abs. 1 UStG) and in the consumer's country only for the services §3a Abs. 5 UStG lists, which the order does not tell apart. The declared rate is one rate for the whole order — the destination's standard rate: a line classified as reduced, or charged at any other rate, is refused, and so is a declared rate of 0. It does not model the €10,000 EU-wide distance-sales threshold, OSS-portal registration itself, or the §14a Abs. 2 UStG invoicing-obligation question — those remain the merchant's/their tax advisor's responsibility, entirely outside this row
8Zero-rated domestic supplyDE, DE 123456789DE, B2B or B2CPhotovoltaic installations (§12 Abs. 3 UStG, in force since 2023-01-01) — the only real German zero rateDomesticZ0%BR-Z-10 explicitly forbids a BT-120/121 exemption text/code on a Z line (unlike E/AE/G/K/IC)—Seller VAT-ID (BT-31/BT-32/BT-63) (BR-Z-02); rate 0 (BR-Z-05); amount 0 (BR-Z-09)BR-Z-01, -02, -05, -08, -09, -10
9Mixed rates on one invoiceDE, DE 123456789DE B2BGoods at 19% + books at 7%Domestic, two ratesS (twice, different BT-119)19% and 7%—Two BG-23 VAT breakdown groups, one per rateBR-S-08, BR-S-09 (per breakdown), BR-CO-15, BR-CO-17 (totals)UStG §12, mixed basket
10Credit note for a returnDE, DE 123456789DE B2BFull or partial return of #1Domestic, correctionS19%—Document type code 381 (BR-CL-01); not structurally required, but should carry BT-25 (Preceding Invoice reference) — BR-55 only fires if BG-3 is present at all, so a credit note without any preceding-invoice reference still validates. This is exactly the kind of gap this document is for.BR-S-* (same as #1), BR-55 (conditional)—
11Corrected invoice (not a credit note)DE, DE 123456789DE B2B#1 re-issued with a corrected amount/fieldDomestic, correctionS19%—Document type code 384 (BR-DE-17, XRechnung-specific — not one of EN 16931's own two base codes, 380/381); BT-25 recommended (BR-DE-26, flag="warning" — confirmed empirically: valid: false, accepted: true, see below) when 384 is used — same non-enforcement pattern as row 10's BR-55, on a document our own einvoice-commerce does not support yet (CommerceInvoiceInput.document.kind is only "invoice" or "credit-note")BR-DE-17, BR-DE-26 (both warning, not fatal)UNTDID 1001 (via BR-DE-17's own code list: 326/380/384/389/381/875/876/877)
12DE → EU B2B, serviceDE, DE 123456789Another EU state, B2B, VAT-ID givenService, place of supply in the buyer's stateIntra-EU reverse chargeAE (see ⚠️)0%BT-121 VATEX-EU-AE ("Reverse charge")Seller VAT-ID and Buyer VAT-ID (BT-48) (BR-AE-02); "Reverse charge" text/code (BR-AE-10)BR-AE-01, -02, -08, -09, -10§3a Abs. 2 UStG; Art. 44 + 196 VAT Directive. ⚠️ No official artifact example exists — two independent artifact-first research passes over CEN examples, the KoSIT test suite, Peppol examples and the ZUGFeRD corpus found no invoice showing this case with an unambiguous category. While the category is not decided, the code must refuse, not guess — unless the merchant explicitly declares the fact via regimeOverride: { kind: "reverse-charge-cross-border" }: the engine still never infers AE for this row on its own, it only accepts a declared fact, the same pattern row 3's intra-eu-confirmed override already uses for K
13DE → non-EU B2B, serviceDE, DE 123456789Third country, B2BService, place of supply outside GermanyOutside German VATCONTESTED (AE / O / G)0%depends on the code chosen——§3a Abs. 2 UStG. ⚠️ Unresolved. The EC tax-code guidance says exports of "goods and services" to a third country are coded G, but no artifact anywhere shows a service line carrying G, and the guidance itself states it "does not represent the views of the Commission". The code refuses (code NON_EU_SERVICE_UNSUPPORTED) instead of falling through to the export branch. No override exists for this row, so such an invoice cannot be issued through this package until the question is resolved

What the validator does not catch (so far) ​

Concrete gaps found while building this table, each traceable to a rule (or the absence of one) above:

  • The buyer's VAT-ID for an intra-community supply (row 3) is required, but its validity is not checkable. Corrected 2026-09-16: an earlier version of this document claimed BR-IC-02 "only requires the seller's VAT-ID". That was wrong. The rule reads "…shall contain the Seller VAT Identifier (BT-31) or the Seller tax representative VAT identifier (BT-63) and the Buyer VAT identifier (BT-48)" and carries flag="fatal" — verified directly against the vendored Schematron. The real gap is different and still real: the validator checks that BT-48 is present, never that it was valid in VIES at the time of supply, which is what the Art. 138 exemption actually depends on. So einvoice-commerce must still verify the ID itself; a green validator proves the field is filled in, nothing more. The seller has to be able to show that check later: the Medusa plugin keeps the VIES answer with every document of category K (What the plugin stores).

  • A cross-border B2B service (row 12) could not be invoiced at all before the reverse-charge-cross-border override existed. The default rule refuses while the category is not decided (no artifact example), and row 5's domestic reverse-charge override explicitly rejects any non-DE buyer — so a German seller of a cross-border EU B2B service (SaaS, consulting, other digital services — an ordinary scenario for this project's audience) had no way to declare the fact and get a document out. regimeOverride: { kind: "reverse-charge-cross-border" } closes this as a new, separate override kind, not an extension of row 5's — §13b UStG (domestic) and §3a Abs. 2 UStG / Art. 44+196 (cross-border) are different legal bases, and the engine still never infers AE for this row on its own; only a merchant-declared fact reaches it.

  • An OSS-registered merchant had no way to declare it (row 7) before the Medusa adapter's ossRegistered option existed. decideVatCategory's row-7 branch already required ossRegistered: true and an explicit ossRateOverride, but the Medusa adapter hardcoded ossRegistered: false for every order — no config or order field fed it at all, so the branch could never fire. The fix is at the adapter layer only (tax-rules.ts itself was already correct, proven by its own pre-existing unit tests): EinvoiceModuleOptions.ossRegistered (merchant-level, mirrors seller/payment) supplies the registration fact, and order.metadata.oss_rate_override (order-level, mirrors regime_override's own placement) supplies the rate — a two-tier split, since registration is a standing fact about the seller and the rate is specific to one order's buyer country. The scope stays deliberately narrow: declared registration plus a declared rate, nothing about distance-sales thresholds, OSS-portal registration, or the invoicing obligation itself (see row 7's own table cell above).

  • Profile choice is the recipient's expectation, not the buyer's country as a gate. ✅ Fixed 2026-09-19. Before that, selectProfile() refused any non-German buyer country outright — not only DK/NO/SE as its own doc comment claimed, but France, Switzerland, the UK, everything (found during the first run of the tax-matrix fixtures, packages/einvoice-medusa/fixtures/tax-matrix). A German seller invoicing a French or Dutch business (rows 3–4) needs no national CIUS of the buyer's country: the EN 16931 hybrid (Factur-X = ZUGFeRD EN 16931) is accepted EU-wide, so selectProfile now resolves it (or the caller's own preferredProfile) for any EU/EEA member state and for Switzerland/the UK too — neither runs a clearance system of its own. Only clearance-model countries (IT SDI, PL KSeF) cannot be served by any EN 16931 document at all — those are still refused explicitly, with a distinct message naming the reason, not silently and not with the generic "not yet supported" text. A buyer country outside all of the above (e.g. the US, row 13) is still refused with that generic text — v0.1 has no reviewed e-invoicing basis for it. Seller-side jurisdiction (tax rules) is unaffected and stays DE-only until v0.2 (packages/einvoice-commerce/src/profile.ts).

  • A credit note is never structurally forced to reference the invoice it corrects (row 10). BR-55 only requires BT-25 to be non-empty if a preceding-invoice-reference group is present at all — it never requires the group itself. einvoice-commerce's credit-note path must make BT-25 mandatory by its own rule, not rely on the validator.

  • Category E (exempt, row 6) has no single universal exemption code. Unlike K/AE/G (each with one fixed VATEX-* code tied 1:1 to a BR-*-10 rule), which specific UStG §4 exemption applies is a case-by-case legal judgment einvoice-commerce cannot resolve generically — the caller supplies the exemption reason, and the software refuses to guess.

  • OSS distance sales (row 7) use the same category S as a domestic sale, just with a different rate — nothing in the EN 16931 rule set signals that the rate came from OSS logic rather than a plain domestic rate. Getting the rate lookup wrong (using DE's 19% instead of the buyer country's rate) produces a document that validates perfectly and is still wrong.

  • A reduced rate (row 2) is just a lower percentage inside the same category S. Nothing distinguishes "we deliberately applied 7% because this is a book" from "we made an arithmetic mistake and typed 7 instead of 19" — TaxDecision.reasoning is the only place this becomes auditable. Where the shop records the rate it charged on a line (CommerceLine.chargedVatRate; the Medusa adapter passes its tax lines' rate), buildInvoice refuses a domestic line charged at anything but 19% or 7%, and one classified as the other rate, instead of invoicing a rate the buyer was not charged.

  • A Leitweg-ID's own check digit is never validated by KoSIT. BR-DE-15 (verified against the real rule text in the built KoSIT Docker image, not recalled) only requires BT-10 (Buyer reference) to be non-empty — it has no opinion on whether the value is a well-formed Leitweg-ID at all. A mistyped Leitweg-ID (right shape, wrong check digit — or simply a typo that produces a different but still plausible-looking German public-sector routing address) passes KoSIT cleanly and only fails once it reaches the receiving system and misroutes. einvoice-commerce closes this specific gap itself (leitweg-id.ts) — not because KoSIT asked it to, but because KoSIT structurally cannot: the real ISO/IEC 7064 MOD 97-10 checksum is outside EN 16931's own rule vocabulary entirely, it's a German national convention layered on top of BT-10. The Leitweg-ID has to be declared as such (references.leitwegId; in Medusa, customer.metadata.leitweg_id): BT-10 is free text for every other buyer, and an ordinary reference such as 2024-01 has a Leitweg-ID's shape, so the shape alone says nothing about which one a value is.

  • Invoice number uniqueness and gap-free sequencing are never checked by any single-document validator.BR-02 (verified the same way) only requires BT-1 to be non-empty — any string satisfies it, including a duplicate of an already-issued number or one that skips ten values. This isn't a EN 16931 oversight; it's structurally out of reach for a schematron that validates one document at a time and never sees the others. German law requires every invoice number to be unique (§14 Abs. 4 Nr. 4 UStG); a gap-free sequence is not required (UStAE 14.5 Abs. 10). Corrected 2026-09-23: an earlier version of this document claimed GoBD requires gap-free numbering — it does not. Nothing in the XRechnung/KoSIT toolchain enforces uniqueness either — einvoice-commerce's numbering.ts allocates sequential numbers (SequentialNumberer) for numbers this package itself allocates; a caller-supplied document.number is trusted as-is. InMemoryNumberingStore starts again from 1 after a restart and is for tests only. The Medusa plugin takes a number only after every check buildInvoice makes has passed, so a refused order leaves no gap; a failure after that, such as storing the files, can.

  • A credit note (381) and a corrected invoice (384) are two different EN 16931/XRechnung document types with almost the same non-enforcement pattern, and einvoice-commerce only implements one of them (row 11). BR-DE-26 (also verified against the real rule text — and, like BR-DE-17, only a flag="warning", not fatal) recommends but does not require BT-25 when code 384 is used, mirroring row 10's BR-55 gap for credit notes almost exactly. The two codes are not interchangeable in practice — 381 is a new financial document (a return, a rebate) that stands on its own; 384 replaces or amends a previously issued invoice's content. CommerceInvoiceInput.document.kind ("invoice" | "credit-note") has no way to express the 384 case at all yet — a real, documented gap, not a silent omission.

  • "KoSIT accepted it" and "the report's valid attribute is true" are not the same claim, and this repo's own tooling only checked the second one until now. Confirmed by actually running the row-11 document (code 384, no BT-25) through the real KoSIT validator: the report's own <rep:report valid="…"> attribute is false (any warning-level message flips it, not only fatal ones), yet <rep:assessment><rep:accept> and the CLI's own human-readable "Acceptable: 1, Rejected: 0" both say the document is fine to process. Every conformance gate in this repo (pnpm conformance:fixtures and friends) asserts the strict valid field, deliberately — a document with an unaddressed warning does not count as "green" here even if KoSIT itself would forward it — but until this was found, our own einvoice-conformance package could not even report the distinction: parseKositReport() now exposes it as a separate accepted field (kosit-report.ts) rather than silently equating "valid" with "accepted".

  • The word "Gutschrift" is ambiguous in German VAT law. UStG §14 Abs. 2 uses Gutschrift for a self-billed invoice (UNTDID 1001 code 389, issued by the buyer). A credit note (381) whose human-readable title says "Gutschrift" can be misread as self-billing, with §14c exposure. The XML is unambiguous (type code 381); the PDF title is not. einvoice-pdfa's renderInvoicePdf therefore titles 381 documents "Rechnungskorrektur / Credit note", never "Gutschrift", and prints the invoice they correct under the title (a PDF of your own is yours to title) — the exact wording is chosen conservatively (BMF-Schreiben 2013 does permit "Gutschrift" in an unambiguous context, but common practice avoids it); confirm the final phrasing with your own tax advisor before relying on it.

  • Shipping and discounts take the rate of the supply they belong to — a validator cannot tell. Shipping charged by the seller is an ancillary supply that shares the main supply's rate (Art. 78(b) VAT Directive, §10 Abs. 1 UStG, UStAE 3.10 Abs. 5), and a discount reduces the base of the supplies it relates to (§17 UStG). Taxing a books-only order's shipping at 19% instead of 7%, or an OSS order's shipping at Germany's rate instead of the destination's, still produces a VAT breakdown whose amounts add up — BR-S-08/BR-S-09 pass. buildInvoice therefore gives shipping and document-level discounts the rate of the lines when all lines share one rate (7%, 19%, or the declared OSS rate). A discount that belongs to one line (a promotion on one item) is expressed on that line (BG-27) and reduces only that line's rate. When lines carry different rates, the shipping serves supplies at each of them, so buildInvoice splits shipping and every document-level discount across the rates in proportion to the lines' net amounts at each rate, after their own allowances — one charge (BG-21) or allowance (BG-20) per rate, its reason naming the share ("Versand / Shipping (anteilig 7 %)"), the cents left over going to the largest remainder. No provision rules on shipping at mixed rates directly; this follows the ancillary supply sharing the fate of its main supplies, and UStAE 10.1 Abs. 11's split of a single price across supplies at different rates. Shipping at the standard rate would overstate the VAT of the 7% part (§14c Abs. 1 UStG for a business buyer). With prices that include VAT, the gross shipping is split in the same proportion and each rate's VAT taken out of its share; the VAT per rate is computed on the group (BR-CO-17), so it can differ by a cent from VAT computed per line. Medusa taxes shipping at one rate, so a mixed order's invoice states less VAT than Medusa charged and carries a notice naming this cause (see below).

  • An intra-EU supply (row 3) and an export (row 4) depend on where the goods go, not only on who buys. Validators check that BT-80 is present for K (BR-IC-12), never that it names another member state, and nothing ties the positive VIES check to the VAT-ID printed on the document. buildInvoice refuses category K when the goods are delivered to Germany or outside the EU (§6a Abs. 1 Nr. 1 UStG), when the buyer's VAT-ID is German (§6a Abs. 1 Nr. 4 UStG — it must be issued by another member state), when the VIES evidence is for a different VAT-ID, or when the document's BT-48 differs from the VAT-ID the decision was made on. It refuses category G when the goods are delivered inside the EU, even to a buyer established outside it (§6 Abs. 1 UStG).

  • A country code can misstate where the VAT area ends. The Canary Islands arrive as ES, Northern Ireland as GB, Heligoland as DE — and each would get a category every validator accepts and that is wrong: an intra-EU supply that is an export, an export that is an intra-EU supply (Northern Ireland is inside the EU VAT area for goods under the Windsor Framework), a domestic sale at 19 % that leaves the German VAT area (§1 Abs. 2 UStG). buildInvoice recognises these territories by postcode and refuses the document (SPECIAL_VAT_TERRITORY) instead of deciding a category: Heligoland (27498) and Büsingen (78266); the Canary Islands (35xxx, 38xxx), Ceuta (51xxx) and Melilla (52xxx); the Åland Islands (22xxx); the French overseas departments and territories (97xxx, 98xxx except Monaco's 980xx — Monaco is French territory for VAT); Mount Athos (63086, 63087); Livigno (23041) and Campione d'Italia (22061); Northern Ireland (BT, or the country code XI) for goods only — a service to Northern Ireland is a UK service. Goods are placed where they go (the deliver-to address, or the buyer's without one), a service where its buyer is. The Italian waters of Lake Lugano have no postcode and are not recognised. A territory with an ISO code of its own (AX, GP, RE, …) is a third country here, as for VAT it is outside the EU.

  • The exempt (row 6) and zero-rated (row 8) overrides are domestic. Both are declared by the merchant, and both are refused for a buyer outside Germany — otherwise a declared E or Z would skip the VIES check of row 3 and the refusals of rows 12/13 entirely. §12 Abs. 3 UStG covers private operators of photovoltaic installations too, so row 8 is not limited to business buyers.

  • A cross-border B2B service (row 12) needs the buyer's VAT-ID on the invoice. BR-AE-02 accepts a buyer legal registration identifier (BT-47) instead of the VAT-ID (BT-48), but §14a Abs. 1 UStG requires the VAT identification numbers of both parties on this invoice; buildInvoice refuses row 12 without BT-48.

  • What a credit note credits is not checked against what was refunded (row 10). A credit note that restates the whole order for a refund of a tenth of it validates perfectly and reduces the VAT base by ten times too much (§17 UStG). The Medusa adapter credits a refund for its own amount: a refund that covers the invoice, with nothing credited before, restates the invoice; any other refund is credited over the refunded gross amount, at the invoice's own category; nothing is ever credited beyond what is still outstanding on the invoice. A credit note states what it credits at each rate (§14 Abs. 4 Nr. 7–8 UStG, §31 Abs. 5 UStDV), so a partial credit on an order with lines at different rates is split: the money first pays for goods the buyer sent back and the shop received, not credited yet, in the order they were received, at their own lines' rates (the platform does not link a refund to a return, so coverage goes by order of receipt, not by matching amounts); what is left — a goodwill refund, shipping paid back — reduces each rate in proportion to what is still uncredited at it. One line per rate and kind ("Rückgabe / Return", "Teilerstattung / Partial refund (anteilig 7 %)"); which returns a credit note paid for is stored with it, so a later refund does not pay for them again. A cancelled fulfillment's invoice gets a credit note for whatever is outstanding on it, at each rate; so does an invoice still standing when the order is cancelled.

  • A credit note corrects the supply as it was invoiced, not as the order looks today (row 10). Between the invoice and a refund the buyer's VAT-ID can stop being valid, the shop can register for OSS, the order's address can be edited — decided again, a credit note would get another category than the invoice it corrects, or none. The Medusa adapter keeps the tax decision with each invoice and a credit note follows it (correctedInvoiceDecision of buildInvoice): the invoice's category, exemption reason and rule, and the VIES answer the invoice rested on — no second VIES check. The credit note itself is still checked: a K credit note needs the buyer's VAT-ID and a delivery to another member state on the document. An invoice issued before decisions were kept is decided again, as before.

  • Two invoices for one supply validate, each on its own. An invoice for goods already invoiced makes the VAT on it owed a second time (UStAE 14c.1 Abs. 4, BFH XI R 54/93), and a validator sees one document at a time. The Medusa adapter invoices each fulfillment for the lines and units it shipped, dated the day it shipped (BT-72, §14 Abs. 4 Nr. 6 UStG): an order shipped at once gets one invoice, an order shipped in parts one per part. The order's shipping goes on its first invoice — split by the whole order's rates when it has two — and a line's discount is shared out by units, the invoice that ships its last units taking what is left, so a line's invoices add up to it. A refund with goods that came back credits the invoice that holds them. Money back with no goods, while the order has several invoices open or a part not shipped yet, is not credited by the plugin: it may be for goods never shipped, which needs no credit note, and it cannot be told from goodwill on one of the invoices — the refusal says so.

  • A validator accepts an invoice without a street address. §14 Abs. 4 Satz 1 Nr. 1 UStG requires the full name and the full address of both the seller and the buyer; EN 16931 makes the street (BT-35 for the seller, BT-50 for the buyer) optional, and KoSIT checks only city, post code and country. buildInvoice refuses a seller without a street (MissingSellerAddressError) — the seller's full address is required on a small-amount invoice too (§33 UStDV) — and warns (buyer-street-missing) when the buyer has none on an invoice above EUR 250 including VAT. It cannot make a buyer's street up. The Medusa adapter takes the street from the billing address (address_1, address_2), and names the buyer (BT-44) after it too — its company, else the person's first and last name — falling back to the customer record and, last, the email.

  • A validator accepts an invoice that totals a cent less than the buyer paid. With prices that include VAT, rounding each line's net amount to the cent and computing the VAT on their sum can land a cent below the gross prices charged — 10.00 + 10.00 at 19% gave 19.99. Given VAT-inclusive prices (priceInclVat, amountInclVat), buildInvoice takes each rate group's VAT out of its gross total instead, so the invoice totals exactly what was charged. The group's VAT can then differ from its taxable amount × rate by a cent, which BR-CO-17 and BR-S-09 allow (both accept a difference below 1).

  • A validator cannot tell a Kleinunternehmer's invoice from any other. A Kleinunternehmer (§19 UStG) charges no VAT, and need not issue e-invoices at all: an invoice for their sales may always be sent as an ordinary invoice (§34a Satz 4 UStDV). Invoicing their sale as category S would state VAT that is then owed (§14c Abs. 1 UStG); no category of this release fits it. The plugin does not support Kleinunternehmer. A domestic order on which the shop charged no VAT at all — no line and not its shipping above 0 %, or no tax line anywhere — is refused as NO_VAT_CHARGED, which names both causes: a shop whose tax settings have no German rate, and a Kleinunternehmer. One untaxed line among taxed ones is a settings error and keeps its own code.

  • A validator accepts a rate that was not in force on the day of the supply. Germany's rates were 16 % and 5 % from 2020-07-01 to 2020-12-31 (§ 28 Abs. 1 and 2 UStG) and 19 % and 7 % either side; a supply is taxed at the rate of the day it is made. buildInvoice reads the rates of the supply date (BT-72) from a table with the periods since 2007 (de-vat-rates.ts), each quoting its norm, checked against the consolidated UStG (sources.md). An order charged at one period's rate and delivered in another is refused (RATE_NOT_IN_FORCE_ON_SUPPLY_DATE) rather than invoiced at either. A credit note credits each line at the rate its invoice stated — §17 UStG corrects the supply as it was invoiced — not at the rate the shop's tax lines or today's table give, and states the invoice's delivery date. The table ships with the package: a new rate reaches a shop only with a release, and a weekly check of § 12 and § 28 opens an issue in this repository when either changes.

  • A validator cannot tell a replacement from a sale. Medusa ships an exchange's new item and a warranty claim's replacement as ordinary order lines at the catalogue price. An invoice for a warranty replacement states VAT that is then owed (§14c Abs. 1 UStG) for a supply that needs no invoice; an invoice for an exchange's new item without the returned one reversed double-counts the supply. The plugin refuses both shipments, and a credit note for a refund on such an order — the refund settles the exchange or the postage of a warranty case (§439 Abs. 2 BGB), not a price reduction. See features/einvoice-medusa.md.

  • A validator cannot see a price edited after the invoice. An order edit can change a shipped line's price; the invoice keeps the one it stated. The plugin notes the change on the invoice — a lowered price is credited when the difference is refunded, a raised one needs an additional invoice outside the plugin.

  • A validator cannot see what the buyer was charged. An invoice whose VAT differs from the VAT the shop charged validates as long as its own sums add up. The two differ when the shop decided VAT at checkout by region alone while the invoice decides it from the buyer's VAT-ID and the destination — Medusa's built-in tax provider knows nothing of the intra-EU reverse charge. VAT stated on an invoice is owed, whatever was charged (§14c Abs. 1 UStG), so the Medusa adapter compares the invoice's VAT (BT-110) with order.tax_total and its total (BT-112) with order.total plus order.credit_line_total — Medusa subtracts credit lines from the total, and they are payments (store credit, gift cards, and every refund Medusa records), not price reductions — before it takes a document number:

    • they agree within rounding (a cent for each independently rounded amount) → issued;
    • net prices, and the invoice states less VAT, which explains the whole difference → issued with a notice. The buyer paid that VAT on top and overpaid; the notice says how much to refund. Returning the overpayment brings the payment down to a correct invoice, so no credit note is needed (§17 UStG): a refund first returns the overpayment and credits only what goes beyond it;
    • gross prices, the totals agree and only the VAT differs, in either direction → issued with a notice. The buyer paid the invoice total; the consideration is what was received less the VAT (§10 Abs. 1 UStG), so the VAT the invoice takes out of it is owed whatever Medusa computed, and §14c Abs. 1 applies to VAT above what the law requires, not above Medusa's figure. Nothing is to be refunded; only Medusa's VAT figure is wrong — take VAT from the invoices, not from Medusa's order totals. Where Medusa counts less VAT than the invoice, its reports understate the VAT owed; the usual cause is a shipping option or region without the right tax rate;
    • when the invoice splits shipping across the order's VAT rates and Medusa taxed it at one, the notice names that as the cause;
    • anything else — the invoice would state more VAT than was charged and a higher total than was paid, or the totals differ for another reason, or the order mixes prices with and without VAT → not issued. The reason, with the amounts, is shown on the order, and the invoice can be issued again once the order is corrected.

    Credit notes are not compared: they are built from what was refunded, not from the order's total. Credit lines are not shown as a paid amount (BT-113) on the invoice: it states the full consideration, and a paid amount of the voucher part alone would make the amount due (BT-115) wrong. The one payment an invoice states is a full one: when what Medusa captured, less refunds, covers the order's total before it ships, each of its invoices states its own total as paid and nothing due.

This list is a starting point, not exhaustive — it grows as scenarios are added.

DK/NO/SE identifiers (out of v0.1 scope — documented, not implemented) ​

This section names what v0.1 deliberately does not cover, rather than leave the gap silent. v0.1 targets Germany only as a seller jurisdiction; decideVatCategory() refuses a non-German seller outright rather than guessing at a jurisdiction's VAT rules it was never reviewed against (tax-rules.ts). Buyer geography is a separate axis: since 2026-09-19, selectProfile() resolves the EN 16931 hybrid profile for a DK/NO/SE buyer the same way it does for any other EU/EEA buyer — it no longer refuses them. What genuinely stays out of v0.1 scope, gated on einvoice-ubl existing at all, is the DK/NO/SE-specific identifier mapping below and a PEPPOL_BIS profile preference for those buyers — selectProfile has no third profile value yet, only "EN16931" | "XRECHNUNG".

Denmark, Norway, and Sweden each use their own national business-registry identifier as the Peppol participant-ID scheme, already present in this repo's own vendored EasCode codelist (BT-34/BT-49 electronic address scheme, sourced from the EN 16931 CII Schematron's BR-CL-25) even though nothing yet maps a DK/NO/SE party to one — confirmed directly against the official Peppol ICD code list, not assumed from the code values alone:

CountryICD codeScheme name (verbatim from the Peppol registry)
Denmark0184DIGSTORG (Danish CVR-registered companies, incl. legal persons and associations)
Norway0192Organisasjonsnummer (Central Coordinating Register for Legal Entities)
Sweden0007Organisationsnummer (issued by the Swedish National Tax Board)

Norway's own B2B e-invoicing mandate takes effect 2027-01-01. Denmark's and Sweden's mandate specifics have not been researched for this repository — recorded as unresearched, not guessed.