German VAT on used parts (Altteilesteuer)
A rule of German VAT law: the workshop supplies an exchange engine, the customer hands back the old one – and VAT is due on the value of that used part, although it is never charged. The result is an invoice whose tax amount is higher than 19 % of the net total.
This page describes German VAT law. The “Altteilesteuer” exists in Germany only and does not apply to invoices under the VAT law of other countries. It is based on § 3(12) and § 10(2) sentence 2 of the German VAT Act (Umsatzsteuergesetz, UStG) – barter with an additional cash payment – and on section 10.5(3) of the German VAT Application Decree (Umsatzsteuer-Anwendungserlass, UStAE), which covers the exchange procedure in the motor vehicle trade. This page shows how to represent the case in an electronic invoice; it is not tax advice.
The representation follows business case 03.06a of the
XRechnung test suite published by KoSIT, the German Coordination
Office for IT Standards:
03.06a-INVOICE_uncefact.xml.
Line items, VAT categories and totals are identical; the master data is that
of the other sample invoices in this documentation.
What is this about?
In the exchange procedure the workshop supplies a working part and receives two things in return: money and the used part in need of repair. For German VAT purposes this is a barter with an additional cash payment – so the consideration for the exchange part consists of the cash payment plus the value of the used part. The UStAE allows that value to be set at a flat 10 % of the so-called gross exchange price (Bruttoaustauschentgelt).
In the example the engine costs EUR 1,000. The used part is therefore worth EUR 100, which gives rise to EUR 19 of VAT. The customer does not pay the EUR 100 itself – it has been settled by handing over the used part. The EUR 19, however, appear on the invoice.
EN 16931 has no dedicated field for this. The KoSIT test suite solves it with two additional line items that cancel each other out in the net amount, but not in the tax:
| Line | Description | Quantity | Line total | Category | Tax |
|---|---|---|---|---|---|
| 1 | Neumotor (new engine) | 1 | 1,000.00 | S, 19 % | 190.00 |
| 2 | Bemessungsgrundlage und Umsatzsteuer auf Altteil (taxable amount and VAT on the used part) | 1 | 100.00 | S, 19 % | 19.00 |
| 3 | Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil (correction of that taxable amount) | −1 | −100.00 | Z, 0 % | 0.00 |
| 4 | Montage (assembly) | 1 | 500.00 | S, 19 % | 95.00 |
| Total | 1,500.00 | 304.00 |
Line 2 brings the value of the used part into the taxable amount of the standard rate. Line 3 takes the same EUR 100 net out again, but carries category Z at 0 % – so the tax from line 2 stays. The bottom line: EUR 1,500 net, EUR 304 VAT, EUR 1,804 gross.
What this example shows
- A line item with a negative quantity and a positive price in an ordinary invoice (type 380)
- Two VAT categories in one document: S (19 %) and Z (0 %)
- A VAT breakdown with a negative taxable amount (BT-116)
- A tax amount (BT-110) that deliberately does not equal 19 % of the net total (BT-109)
The code below builds the document step by step. If you are new to FactoorSharp, Getting started is the better entry point – the focus here is on what makes the Altteilesteuer special. For every BT and BG number you will find the corresponding XML element in the Factur-X reference.
1. Invoice header
A perfectly ordinary commercial invoice. The note with the code TXD
is optional and not part of the KoSIT original; it tells the recipient in one
sentence where the additional EUR 19 come from.
FacturXInvoice invoice = FacturXInvoice.CreateInvoice(
invoiceNo: "R87654321012346",
invoiceDate: new DateTime(2025, 10, 1),
currency: CurrencyCodes.EUR);
// BT-3: 380 = commercial invoice. The Altteilesteuer needs no document type of its own.
invoice.Type = InvoiceType.Invoice;
invoice.Name = "RECHNUNG"; // BT-X-2, EXTENDED only
// Optional: the code TXD (tax information) tells the human reader
// why the tax amount is higher than 19 % of the net total.
invoice.AddNote("Lieferung im Austauschverfahren. Die Umsatzsteuer auf das Altteil ist "
+ "nach Abschn. 10.5 Abs. 3 UStAE gesondert berechnet "
+ "(Altteilwert 100,00 EUR, 19 % = 19,00 EUR).",
subjectCode: SubjectCodes.TXD);
Dim invoice As FacturXInvoice = FacturXInvoice.CreateInvoice(
invoiceNo:="R87654321012346",
invoiceDate:=New DateTime(2025, 10, 1),
currency:=CurrencyCodes.EUR)
' BT-3: 380 = commercial invoice. The Altteilesteuer needs no document type of its own.
invoice.Type = InvoiceType.Invoice
invoice.Name = "RECHNUNG" ' BT-X-2, EXTENDED only
' Optional: the code TXD (tax information) tells the human reader
' why the tax amount is higher than 19 % of the net total.
invoice.AddNote("Lieferung im Austauschverfahren. Die Umsatzsteuer auf das Altteil ist " &
"nach Abschn. 10.5 Abs. 3 UStAE gesondert berechnet " &
"(Altteilwert 100,00 EUR, 19 % = 19,00 EUR).",
subjectCode:=SubjectCodes.TXD)
2. Seller and buyer
The master data is irrelevant to the Altteilesteuer. It matches that of the Extended goods invoice.
// Seller (BG-4): the workshop that supplies and fits the exchange part.
invoice.SetSeller(
name: "MUSTERLIEFERANT GMBH",
postcode: "99199",
city: "MUSTERHAUSEN",
street: "BAHNHOFSTRASSE 99",
country: CountryCodes.DE,
id: "549910",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"));
invoice.SetSellerContact(
emailAddress: "max.mustermann@musterlieferant.de",
phoneno: "+49 932 431 500");
// BT-31: VAT identification number of the seller
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA);
// Buyer (BG-7): hands back the used part.
invoice.SetBuyer(
name: "MUSTER-KUNDE GMBH",
postcode: "40235",
city: "KUNDENSTADT",
street: "KUNDENWEG 88",
country: CountryCodes.DE,
id: "009420",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"));
invoice.ActualDeliveryDate = new DateTime(2025, 10, 1); // BT-72
' Seller (BG-4): the workshop that supplies and fits the exchange part.
invoice.SetSeller(
name:="MUSTERLIEFERANT GMBH",
postcode:="99199",
city:="MUSTERHAUSEN",
street:="BAHNHOFSTRASSE 99",
country:=CountryCodes.DE,
id:="549910",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"))
invoice.SetSellerContact(
emailAddress:="max.mustermann@musterlieferant.de",
phoneno:="+49 932 431 500")
' BT-31: VAT identification number of the seller
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA)
' Buyer (BG-7): hands back the used part.
invoice.SetBuyer(
name:="MUSTER-KUNDE GMBH",
postcode:="40235",
city:="KUNDENSTADT",
street:="KUNDENWEG 88",
country:=CountryCodes.DE,
id:="009420",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"))
invoice.ActualDeliveryDate = New DateTime(2025, 10, 1) ' BT-72
3. The exchange part
Line 1 is unremarkable: one piece, standard rate.
// Line 1: the exchange part. 1 × 1,000.00 = 1,000.00, 19 %.
invoice.AddTradeLineItem(
name: "Neumotor",
netUnitPrice: 1000.00m,
grossUnitPrice: 1000.00m,
unitCode: QuantityCodes.C62,
billedQuantity: 1m,
lineTotalAmount: 1000.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
' Line 1: the exchange part. 1 × 1,000.00 = 1,000.00, 19 %.
invoice.AddTradeLineItem(
name:="Neumotor",
netUnitPrice:=1000D,
grossUnitPrice:=1000D,
unitCode:=QuantityCodes.C62,
billedQuantity:=1D,
lineTotalAmount:=1000D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
4. The used part: two line items
This is where the actual representation happens. Both lines carry the same price of EUR 100; they differ in quantity and VAT category.
The sign sits in the quantity, not in the price. Business rule BR-27 of EN 16931 forbids a negative net price (BT-146); a negative quantity (BT-129), and with it a negative line total (BT-131), is permitted.
// Line 2: the value of the used part (10 % of 1,000.00) enters the taxable amount
// of the standard rate: 1 × 100.00 = 100.00, 19 % → 19.00 tax.
invoice.AddTradeLineItem(
name: "Bemessungsgrundlage und Umsatzsteuer auf Altteil",
netUnitPrice: 100.00m,
grossUnitPrice: 100.00m,
unitCode: QuantityCodes.C62,
billedQuantity: 1m,
lineTotalAmount: 100.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
// Line 3: takes the 100.00 net out again – with category Z and 0 %,
// so that the 19.00 tax from line 2 stays.
invoice.AddTradeLineItem(
name: "Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil",
netUnitPrice: 100.00m, // positive! (BR-27)
grossUnitPrice: 100.00m, // positive!
unitCode: QuantityCodes.C62,
billedQuantity: -1m, // negative!
lineTotalAmount: -100.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.Z,
taxPercent: 0m); // state 0 % explicitly (BR-Z-05)
' Line 2: the value of the used part (10 % of 1,000.00) enters the taxable amount
' of the standard rate: 1 × 100.00 = 100.00, 19 % → 19.00 tax.
invoice.AddTradeLineItem(
name:="Bemessungsgrundlage und Umsatzsteuer auf Altteil",
netUnitPrice:=100D,
grossUnitPrice:=100D,
unitCode:=QuantityCodes.C62,
billedQuantity:=1D,
lineTotalAmount:=100D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
' Line 3: takes the 100.00 net out again – with category Z and 0 %,
' so that the 19.00 tax from line 2 stays.
invoice.AddTradeLineItem(
name:="Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil",
netUnitPrice:=100D, ' positive! (BR-27)
grossUnitPrice:=100D, ' positive!
unitCode:=QuantityCodes.C62,
billedQuantity:=-1D, ' negative!
lineTotalAmount:=-100D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.Z,
taxPercent:=0D) ' state 0 % explicitly (BR-Z-05)
The VAT rate of the correction line is passed explicitly as 0m.
Without the parameter BT-152 would stay empty – but for category Z
rule BR-Z-05 requires exactly the value 0.
5. The assembly
Line 4 is ordinary again, this time with the unit HUR for
hours.
// Line 4: the assembly. 1 hour × 500.00 = 500.00, 19 %.
invoice.AddTradeLineItem(
name: "Montage",
netUnitPrice: 500.00m,
grossUnitPrice: 500.00m,
unitCode: QuantityCodes.HUR, // HUR = hour
billedQuantity: 1m,
lineTotalAmount: 500.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
' Line 4: the assembly. 1 hour × 500.00 = 500.00, 19 %.
invoice.AddTradeLineItem(
name:="Montage",
netUnitPrice:=500D,
grossUnitPrice:=500D,
unitCode:=QuantityCodes.HUR, ' HUR = hour
billedQuantity:=1D,
lineTotalAmount:=500D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
6. VAT
The document carries one breakdown per combination of category and rate (BG-23). The taxable amount is the sum of the lines of that category in each case (BR-S-08, BR-Z-08):
| Category | Taxable amount (BT-116) | Tax amount (BT-117) |
|---|---|---|
| S, 19 % | 1,000.00 + 100.00 + 500.00 = 1,600.00 | 1,600.00 × 19 % = 304.00 |
| Z, 0 % | −100.00 | 0.00 |
// Category S, 19 %: lines 1, 2 and 4 → 1,000.00 + 100.00 + 500.00 = 1,600.00
invoice.AddApplicableTradeTax(
basisAmount: 1600.00m,
percent: 19m,
taxAmount: 304.00m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S);
// Category Z, 0 %: line 3 only → taxable amount −100.00, tax 0.00
invoice.AddApplicableTradeTax(
basisAmount: -100.00m,
percent: 0m,
taxAmount: 0.00m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.Z);
' Category S, 19 %: lines 1, 2 and 4 → 1,000.00 + 100.00 + 500.00 = 1,600.00
invoice.AddApplicableTradeTax(
basisAmount:=1600D,
percent:=19D,
taxAmount:=304D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S)
' Category Z, 0 %: line 3 only → taxable amount −100.00, tax 0.00
invoice.AddApplicableTradeTax(
basisAmount:=-100D,
percent:=0D,
taxAmount:=0D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.Z)
In the XML the two breakdowns sit side by side. The negative taxable amount is not a mistake but the core of the representation:
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>304.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>1600.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>0.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>-100.00</ram:BasisAmount>
<ram:CategoryCode>Z</ram:CategoryCode>
<ram:RateApplicablePercent>0.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
7. Payment terms
Every invoice with a positive amount due needs information on when payment is due (BR-CO-25) – either as a date (BT-9) or as text (BT-20). Here both are given.
// BT-20 and BT-9: payment terms as free text plus the due date.
invoice.AddTradePaymentTerms(
description: "Zahlbar innerhalb 30 Tagen ohne Abzug.",
dueDate: new DateTime(2025, 10, 31));
' BT-20 and BT-9: payment terms as free text plus the due date.
invoice.AddTradePaymentTerms(
description:="Zahlbar innerhalb 30 Tagen ohne Abzug.",
dueDate:=New DateTime(2025, 10, 31))
8. Totals
The totals follow from the line items and the two breakdowns:
| BT code | Parameter | Calculation |
|---|---|---|
| BT-106 | lineTotalAmount |
1,000.00 + 100.00 − 100.00 + 500.00 = 1,500.00 |
| BT-109 | taxBasisAmount |
1,600.00 + (−100.00) = 1,500.00 |
| BT-110 | taxTotalAmount |
304.00 + 0.00 = 304.00 |
| BT-112 | grandTotalAmount |
1,500.00 + 304.00 = 1,804.00 |
| BT-115 | duePayableAmount |
1,804.00 |
19 % of 1,500.00 would be 285.00 – the document states 304.00. The difference of 19.00 is exactly the Altteilesteuer. Validation does not object: EN 16931 checks the tax amount per breakdown (BR-CO-17) and the sum of the breakdowns (BR-CO-14), not the ratio of BT-110 to BT-109.
invoice.SetTotals(
lineTotalAmount: 1500.00m, // BT-106
taxBasisAmount: 1500.00m, // BT-109
taxTotalAmount: 304.00m, // BT-110 – 19.00 more than 19 % of 1,500.00
grandTotalAmount: 1804.00m, // BT-112
duePayableAmount: 1804.00m); // BT-115
invoice.SetTotals(
lineTotalAmount:=1500D, ' BT-106
taxBasisAmount:=1500D, ' BT-109
taxTotalAmount:=304D, ' BT-110 – 19.00 more than 19 % of 1,500.00
grandTotalAmount:=1804D, ' BT-112
duePayableAmount:=1804D) ' BT-115
9. Save and validate
invoice.Save("Altteilesteuer.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII);
invoice.Save("Altteilesteuer.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII)
The representation does not depend on the EXTENDED profile. Line items, taxes and totals can be saved unchanged as BASIC, COMFORT (EN 16931) or XRechnung, in CII as well as in UBL. XRechnung additionally needs its usual mandatory information, among them the Leitweg-ID (BT-10), the name of the contact person (BT-41), the electronic addresses of seller and buyer (BT-34, BT-49) and the payment instructions (BG-16).
Whether the document complies with the rule set is checked by the validator – in the browser or directly from your code. The visualizer produces a human-readable version. Both, along with the detailed documentation, are available in the customer area; how to get there is described under Getting started.