Show reverse charge as a notice instead of a 0% VAT line on invoices
Summary
Related Issue: n/a
B2B customers (tax ID set, VAT reverse-charged) found the VAT (0% Reverse Charge) $0.00 row on invoices confusing. The totals now show only Subtotal / Discount / Total, and a separate line below them reads "Reverse charge: VAT to be accounted for by the recipient."
Examples for finance review
Rendered with the real invoice generator, before (current main) and after (this branch). Customer, VAT IDs and amounts are made up. EU000000000 is a placeholder for Polar's VAT number.
B2B, reverse charge (Belgian company with VAT ID)
| Before | After |
|---|---|
![]() | ![]() |
B2B, reverse charge, with a discount
| Before | After |
|---|---|
![]() | ![]() |
B2C, VAT charged (unchanged, for reference)
Questions for finance
- Is omitting the VAT rate/amount line OK, given the invoice states "Reverse charge" (EU VAT Directive Art. 226(11a)) and shows both parties' VAT IDs?
- Is the wording right? It uses the tax's own name, so a GST reverse charge reads "Reverse charge: GST to be accounted for by the recipient." Should it cite the legal basis (e.g. Art. 196 of Directive 2006/112/EC)? That only fits EU customers, not UK/CH/NO etc.
What
Invoice.tax_itemsonly includes standard-rated tax, so the reverse-charge row (and the "Total excluding tax" line it triggered) is gone.- New
Invoice.reverse_charge_notice, rendered right-aligned and in bold directly below the totals. - Tests for the totals rows and the notice, plus a
reverse_chargePDF render case.
Why
The "0%" row made B2B customers think something was wrong with their invoice. The legal requirement is the "Reverse charge" mention, not a zero-rated tax line.
How
Only the PDF generator (server/polar/invoice/generator.py) changes. Stored tax breakdowns and the backoffice order view (still shows "(0% Reverse Charge)" for admins) are untouched. The example images were committed and removed again in the next commit (d95e854), so they're not part of the diff.
Checklist
- This PR addresses a single concern (one bug fix, one feature, one refactor)
- The diff is reasonably sized and easy to review
- New functionality is covered by tests
- Linting and type checking pass (
uv run task lint && uv run task lint_types) - No unrelated changes or drive-by fixes are included
π€ Generated with Claude Code
https://claude.ai/code/session_01NGQvwEt8ygAKax2TjmnATZ
Generated by Claude Code



