Correction invoice
The correction of an over-billed goods delivery: document type 384, all amounts negative, the unit prices positive. Built up step by step, with an eye on the places where a correction differs from a credit note.
The basis is the official sample invoice
X14_01_Rechnungskorrektur from the Factur-X / ZUGFeRD
documentation (FeRD sample package, ZUGFeRD 2.5.0, EXTENDED profile).
The code below produces exactly that document, field by field and in the
same order.
Correction or credit note?
That is the question most of the time goes into, and it decides every sign in the document. Both kinds of document reduce a receivable, but they do so in opposite ways:
| Correction invoice (384) | Credit note (381) | |
|---|---|---|
| FactoorSharp | InvoiceType.Correction |
InvoiceType.CreditNote |
| Quantities (BT-129) | negative | positive |
| Unit prices (BT-146 / BT-148) | positive | positive |
| Totals, taxes, discounts | negative | positive |
| Reading | The document corrects the original invoice by a difference. | The document mirrors the original invoice as a whole. |
The sign belongs in the quantity, not in the price. A negative unit price with a positive quantity yields the same line total arithmetically, but it is unsound in business terms and many recipients will notice. The reference does it the other way round, and so does this page.
The sections below build on one another and belong to one program. If you are new to FactoorSharp, start with Getting started instead – here the focus is on what makes the correction special. For every BT and BG code, the matching XML element is in the Factur-X reference.
1. Invoice header
Besides number, date and currency, two things stand out: the document type and the business process (BT-23), which this example sets unlike the goods invoice.
FacturXInvoice invoice = FacturXInvoice.CreateInvoice(
invoiceNo: "RK21012345",
invoiceDate: new DateTime(2025, 10, 1),
currency: CurrencyCodes.EUR);
// BT-3: 384 = correction invoice. The decisive difference from 381 (credit note).
invoice.Type = InvoiceType.Correction;
invoice.Name = "RECHNUNGS-KORREKTUR"; // BT-X-2, EXTENDED only
invoice.IsTest = true; // ram:TestIndicator, EXTENDED only
// BT-23: business process → ram:BusinessProcessSpecifiedDocumentContextParameter
invoice.BusinessProcess = "Beispielgeschäftsprozess";
Dim invoice As FacturXInvoice = FacturXInvoice.CreateInvoice(
invoiceNo:="RK21012345",
invoiceDate:=New DateTime(2025, 10, 1),
currency:=CurrencyCodes.EUR)
' BT-3: 384 = correction invoice. The decisive difference from 381 (credit note).
invoice.Type = InvoiceType.Correction
invoice.Name = "RECHNUNGS-KORREKTUR" ' BT-X-2, EXTENDED only
invoice.IsTest = True ' ram:TestIndicator, EXTENDED only
' BT-23: business process → ram:BusinessProcessSpecifiedDocumentContextParameter
invoice.BusinessProcess = "Beispielgeschäftsprozess"
2. Free-text notes
The same kinds of note as in the goods invoice, but in a different order: the
AAK note with a content code comes first here. Since the
cross-check against the reference also compares the order, that is not a detail
but a requirement.
// In this reference the AAK note with a content code comes first.
invoice.AddNote("Es bestehen Rabatt- oder Bonusvereinbarungen.",
subjectCode: SubjectCodes.AAK,
contentCode: ContentCodes.ST3);
invoice.AddNote("Geschäftsführer: Herr Geschäftsführer , Muster Maschinenbau GmbH etc. ",
subjectCode: SubjectCodes.REG);
invoice.AddNote("Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können.",
subjectCode: SubjectCodes.AAI);
invoice.AddNote("ZUGFeRD vers 2.5.0 (Extended)", subjectCode: SubjectCodes.ACB);
invoice.AddNote("Dies ist eine Beispiel-für eine Korrektur einer Rechnung (Type 384)",
subjectCode: SubjectCodes.ACB);
' In this reference the AAK note with a content code comes first.
invoice.AddNote("Es bestehen Rabatt- oder Bonusvereinbarungen.",
subjectCode:=SubjectCodes.AAK,
contentCode:=ContentCodes.ST3)
invoice.AddNote("Geschäftsführer: Herr Geschäftsführer , Muster Maschinenbau GmbH etc. ",
subjectCode:=SubjectCodes.REG)
invoice.AddNote("Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können.",
subjectCode:=SubjectCodes.AAI)
invoice.AddNote("ZUGFeRD vers 2.5.0 (Extended)", subjectCode:=SubjectCodes.ACB)
invoice.AddNote("Dies ist eine Beispiel-für eine Korrektur einer Rechnung (Type 384)",
subjectCode:=SubjectCodes.ACB)
Note texts, product names and discount reasons stay in German throughout this page: they are the payload of the official reference document, and translating them would break the comparison with it.
3. Seller and buyer
Unchanged compared to a normal invoice – a correction does not swap the roles. The seller stays the seller, even when money flows back.
// Seller (BG-4). Unlike the goods invoice, without an electronic address – the
// reference emits no ram:URIUniversalCommunication, hence no
// SetSellerElectronicAddress() either.
invoice.SetSeller(
name: "MUSTERLIEFERANT GMBH",
postcode: "98765",
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");
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA);
// Buyer (BG-7)
invoice.SetBuyer(
name: "MUSTER-KUNDE GMBH",
postcode: "12345",
city: "KUNDENDORF",
street: "KUNDENWEG 88",
country: CountryCodes.DE,
id: "009420",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"));
' Seller (BG-4). Unlike the goods invoice, without an electronic address – the
' reference emits no ram:URIUniversalCommunication, hence no
' SetSellerElectronicAddress() either.
invoice.SetSeller(
name:="MUSTERLIEFERANT GMBH",
postcode:="98765",
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")
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA)
' Buyer (BG-7)
invoice.SetBuyer(
name:="MUSTER-KUNDE GMBH",
postcode:="12345",
city:="KUNDENDORF",
street:="KUNDENWEG 88",
country:=CountryCodes.DE,
id:="009420",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"))
4. Document references
This is where it becomes visible what the correction refers to. Two additional document references (BT-18 / BG-24) with type code 130 carry the complaint process and the original invoice number.
invoice.SetBuyerOrderReferenceDocument("B123456789"); // BT-13
// Two additional document references with type code 130: one for the complaint
// process, one for the original invoice being corrected. The order of the calls
// determines the order in the XML.
invoice.AddAdditionalReferencedDocument(
id: "REKLA-2018-235",
typeCode: AdditionalReferencedDocumentTypeCode.InvoiceDataSheet);
invoice.AddAdditionalReferencedDocument(
id: "R87654321012345",
typeCode: AdditionalReferencedDocumentTypeCode.InvoiceDataSheet);
invoice.SetDeliveryNoteReferenceDocument("L87654321012345"); // BT-16
invoice.ActualDeliveryDate = new DateTime(2025, 10, 1); // BT-72
invoice.SetBuyerOrderReferenceDocument("B123456789") ' BT-13
' Two additional document references with type code 130: one for the complaint
' process, one for the original invoice being corrected. The order of the calls
' determines the order in the XML.
invoice.AddAdditionalReferencedDocument(
id:="REKLA-2018-235",
typeCode:=AdditionalReferencedDocumentTypeCode.InvoiceDataSheet)
invoice.AddAdditionalReferencedDocument(
id:="R87654321012345",
typeCode:=AdditionalReferencedDocumentTypeCode.InvoiceDataSheet)
invoice.SetDeliveryNoteReferenceDocument("L87654321012345") ' BT-16
invoice.ActualDeliveryDate = New DateTime(2025, 10, 1) ' BT-72
For the reference to the corrected invoice there is also a specialised
element, ram:InvoiceReferencedDocument (BT-25/BT-26). The FeRD
reference does not use it here, so it does not appear in this code either
– but if you are not bound to the template, it is the more precise
option.
5. Ship-to party and invoicee
The differing recipients stay exactly as they were on the original invoice.
// Ship-to party (BG-13)
invoice.ShipTo = new Party()
{
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171088093"),
Name = "MUSTER-MARKT",
Postcode = "54321",
Street = "HAUPTSTRASSE 44",
City = "LIEFERSTADT",
Country = CountryCodes.DE
};
// ram:DefinedTradeContact/ram:DepartmentName – OrgUnit is exactly that field.
invoice.ShipToContact = new Contact() { OrgUnit = "8211" };
// Invoicee (BG-X-36, EXTENDED only)
invoice.Invoicee = new Party()
{
ID = new GlobalID(null, "009420"),
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"),
Name = "MUSTER-KUNDE GMBH",
Postcode = "123456",
Street = "KUNDENWEG 88",
City = "KUNDENDORF",
Country = CountryCodes.DE
};
' Ship-to party (BG-13)
invoice.ShipTo = New Party() With
{
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171088093"),
.Name = "MUSTER-MARKT",
.Postcode = "54321",
.Street = "HAUPTSTRASSE 44",
.City = "LIEFERSTADT",
.Country = CountryCodes.DE
}
' ram:DefinedTradeContact/ram:DepartmentName – OrgUnit is exactly that field.
invoice.ShipToContact = New Contact() With {.OrgUnit = "8211"}
' Invoicee (BG-X-36, EXTENDED only)
invoice.Invoicee = New Party() With
{
.ID = New GlobalID(Nothing, "009420"),
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"),
.Name = "MUSTER-KUNDE GMBH",
.Postcode = "123456",
.Street = "KUNDENWEG 88",
.City = "KUNDENDORF",
.Country = CountryCodes.DE
}
6. Invoice lines
This is where the actual correction sits: five bottles and two packs go back. The unit prices stay positive, the quantities turn negative, and that produces the negative line totals.
The item discounts from the original invoice (BT-147) stay positive as well. They are part of the price, not an amount on the invoice: 1.50 − 0.03 − 0.02 = 1.45, and only the negative quantity flips the result.
// Line 1: 5 bottles of citric acid returned → −5 × 1.00 = −5.00, 19 %.
TradeLineItem line1 = invoice.AddTradeLineItem(
lineID: "1",
name: "Zitronensäure 100ml",
netUnitPrice: 1.00m, // positive!
grossUnitPrice: 1.00m, // positive!
unitCode: QuantityCodes.H87,
billedQuantity: -5m, // negative!
lineTotalAmount: -5.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m,
sellerAssignedID: "ZS997",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000014"));
line1.PackageQuantity = 1m;
line1.PackageUnitCode = QuantityCodes.XBO; // XBO = bottle
// Line 2: 2 packs of preserving sugar returned. The item discounts from the original
// invoice still apply and stay POSITIVE – they are part of the price, not an amount:
// 1.50 − 0.03 − 0.02 = 1.45; −2 × 1.45 = −2.90, 7 %.
TradeLineItem line2 = invoice.AddTradeLineItem(
lineID: "2",
name: "Gelierzucker Extra 250g",
netUnitPrice: 1.45m,
grossUnitPrice: 1.50m,
unitCode: QuantityCodes.H87,
billedQuantity: -2m,
lineTotalAmount: -2.90m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 7m,
sellerAssignedID: "GZ250",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"));
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount: null, actualAmount: 0.03m, reason: "Artikelrabatt 1");
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount: null, actualAmount: 0.02m, reason: "Artikelrabatt 2");
line2.PackageQuantity = 1m;
line2.PackageUnitCode = QuantityCodes.XCT; // XCT = carton
' Line 1: 5 bottles of citric acid returned → −5 × 1.00 = −5.00, 19 %.
Dim line1 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="1",
name:="Zitronensäure 100ml",
netUnitPrice:=1.0D, ' positive!
grossUnitPrice:=1.0D, ' positive!
unitCode:=QuantityCodes.H87,
billedQuantity:=-5D, ' negative!
lineTotalAmount:=-5.0D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D,
sellerAssignedID:="ZS997",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000014"))
line1.PackageQuantity = 1D
line1.PackageUnitCode = QuantityCodes.XBO ' XBO = bottle
' Line 2: 2 packs of preserving sugar returned. The item discounts from the original
' invoice still apply and stay POSITIVE – they are part of the price, not an amount:
' 1.50 − 0.03 − 0.02 = 1.45; −2 × 1.45 = −2.90, 7 %.
Dim line2 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="2",
name:="Gelierzucker Extra 250g",
netUnitPrice:=1.45D,
grossUnitPrice:=1.5D,
unitCode:=QuantityCodes.H87,
billedQuantity:=-2D,
lineTotalAmount:=-2.9D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=7D,
sellerAssignedID:="GZ250",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"))
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount:=Nothing, actualAmount:=0.03D, reason:="Artikelrabatt 1")
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount:=Nothing, actualAmount:=0.02D, reason:="Artikelrabatt 2")
line2.PackageQuantity = 1D
line2.PackageUnitCode = QuantityCodes.XCT ' XCT = carton
7. Document-level invoice discounts
The discounts turn around with everything else: a discount on a reversal is a reclaim, so both the basis amount and the amount are negative. As in any invoice with mixed tax rates, each discount needs its own entry per rate – two discounts become four calls.
// Invoice discount 1 – percentage based, 2 %.
invoice.AddTradeAllowance(
basisAmount: -5.00m,
currency: CurrencyCodes.EUR,
actualAmount: -0.10m,
chargePercentage: 2.00m,
reason: "Rechnungsrabatt 1",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
invoice.AddTradeAllowance(
basisAmount: -2.90m,
currency: CurrencyCodes.EUR,
actualAmount: -0.06m,
chargePercentage: 2.00m,
reason: "Rechnungsrabatt 1",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 7m);
// Invoice discount 2 – a fixed amount, therefore without chargePercentage.
invoice.AddTradeAllowance(
basisAmount: -5.00m,
currency: CurrencyCodes.EUR,
actualAmount: -0.05m,
reason: "Rechnungsrabatt 2",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
invoice.AddTradeAllowance(
basisAmount: -2.90m,
currency: CurrencyCodes.EUR,
actualAmount: -0.02m,
reason: "Rechnungsrabatt 2",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 7m);
' Invoice discount 1 – percentage based, 2 %.
invoice.AddTradeAllowance(
basisAmount:=-5D,
currency:=CurrencyCodes.EUR,
actualAmount:=-0.1D,
chargePercentage:=2D,
reason:="Rechnungsrabatt 1",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
invoice.AddTradeAllowance(
basisAmount:=-2.9D,
currency:=CurrencyCodes.EUR,
actualAmount:=-0.06D,
chargePercentage:=2D,
reason:="Rechnungsrabatt 1",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=7D)
' Invoice discount 2 – a fixed amount, therefore without chargePercentage.
invoice.AddTradeAllowance(
basisAmount:=-5D,
currency:=CurrencyCodes.EUR,
actualAmount:=-0.05D,
reason:="Rechnungsrabatt 2",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
invoice.AddTradeAllowance(
basisAmount:=-2.9D,
currency:=CurrencyCodes.EUR,
actualAmount:=-0.02D,
reason:="Rechnungsrabatt 2",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=7D)
8. VAT breakdown
The arithmetic is the same as in any invoice, only with inverted signs. The one
point that trips up readers: a negative discount raises the basis
amount again, which is why allowanceChargeBasisAmount is positive
here.
| Field | 19 % (line 1) | 7 % (line 2) |
|---|---|---|
lineTotalBasisAmount |
−5.00 | −2.90 |
allowanceChargeBasisAmount |
−(−0.10) − (−0.05) = +0.15 | −(−0.06) − (−0.02) = +0.08 |
basisAmount |
−5.00 + 0.15 = −4.85 | −2.90 + 0.08 = −2.82 |
taxAmount |
−4.85 × 19 % = −0.9215 → −0.92 | −2.82 × 7 % = −0.1974 → −0.20 |
invoice.AddApplicableTradeTax(
basisAmount: -4.85m,
percent: 19m,
taxAmount: -0.92m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
allowanceChargeBasisAmount: 0.15m, // positive, because the discounts are negative
lineTotalBasisAmount: -5.00m);
invoice.AddApplicableTradeTax(
basisAmount: -2.82m,
percent: 7m,
taxAmount: -0.20m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
allowanceChargeBasisAmount: 0.08m,
lineTotalBasisAmount: -2.90m);
invoice.AddApplicableTradeTax(
basisAmount:=-4.85D,
percent:=19D,
taxAmount:=-0.92D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
allowanceChargeBasisAmount:=0.15D, ' positive, because the discounts are negative
lineTotalBasisAmount:=-5D)
invoice.AddApplicableTradeTax(
basisAmount:=-2.82D,
percent:=7D,
taxAmount:=-0.2D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
allowanceChargeBasisAmount:=0.08D,
lineTotalBasisAmount:=-2.9D)
9. Document totals
At the end there is a negative payable amount – that is the hallmark of a correction, and for the recipient the statement that money is coming back.
| BT code | Parameter | Calculation |
|---|---|---|
| BT-106 | lineTotalAmount |
−5.00 + (−2.90) = −7.90 |
| BT-107 | allowanceTotalAmount |
−0.10 − 0.06 − 0.05 − 0.02 = −0.23 |
| BT-109 | taxBasisAmount |
−7.90 − (−0.23) = −7.67 |
| BT-110 | taxTotalAmount |
−0.92 + (−0.20) = −1.12 |
| BT-112 | grandTotalAmount |
−7.67 + (−1.12) = −8.79 |
| BT-115 | duePayableAmount |
−8.79 – the amount goes back to the customer |
invoice.SetTotals(
lineTotalAmount: -7.90m, // BT-106
chargeTotalAmount: 0.00m, // BT-108
allowanceTotalAmount: -0.23m, // BT-107
taxBasisAmount: -7.67m, // BT-109
taxTotalAmount: -1.12m, // BT-110
grandTotalAmount: -8.79m, // BT-112
totalPrepaidAmount: 0.00m, // BT-113
duePayableAmount: -8.79m); // BT-115 – the amount goes back to the customer
invoice.SetTotals(
lineTotalAmount:=-7.9D, ' BT-106
chargeTotalAmount:=0D, ' BT-108
allowanceTotalAmount:=-0.23D, ' BT-107
taxBasisAmount:=-7.67D, ' BT-109
taxTotalAmount:=-1.12D, ' BT-110
grandTotalAmount:=-8.79D, ' BT-112
totalPrepaidAmount:=0D, ' BT-113
duePayableAmount:=-8.79D) ' BT-115 – the amount goes back to the customer
10. Saving and checking
Saving works as for any EXTENDED document – version, profile and syntax belong together:
invoice.Save("X14_01_Rechnungskorrektur.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII);
invoice.Save("X14_01_Rechnungskorrektur.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII)
The result has to have the same tree structure as
X14_01_Rechnungskorrektur.xml from the FeRD sample package.
Whether the document also complies with the rule set is what the validator answers – in the browser or straight from your code. A human-readable rendition is produced by the visualizer. Both, and the detailed documentation for them, live in the customer area; how to get there is covered under Getting started.