Invoicing standards and laws have evolved significantly with the digital transformation of business processes. ZUGFeRD is a hybrid electronic invoice format that integrates structured machine-readable data with human-readable PDF/A-3 format. Version 2.3 of ZUGFeRD complies with European standard EN 16931, ensuring compatibility with EU-wide e-invoicing directives. Unlike ZUGFeRD, XRechnung focuses exclusively on structured, machine-readable XML data without incorporating a visual PDF representation.
Factur-X is the French counterpart to ZUGFeRD and uses the same hybrid invoicing concept. Developed jointly by Germany and France, it also complies with the EN 16931 standard. Factur-X combines structured XML data with a PDF/A-3 file, making it versatile for both automated processing and human readability. ZUGFeRD 2.3 and Factur-X are built on the same technical foundation and comply with EN 16931.
ZUGFeRD 2.3 and Factur-X
ZUGFeRD 2.3 and Factur-X are hybrid formats that provide both human-readable and machine-readable components. XRechnung, on the other hand, is a purely structured XML format designed to automate government-related transactions. The pure XML formats are being introduced mainly for government invoices, and it looks like the PDF hybrid versions will be the format of choice for businesses. Both standards are in compliance with the European laws that have been introduced.
This tutorial shows how to create a valid ZUGFeRD 2.3 invoice using ZUGFeRD-csharp, an open source library, and TX Text Control to create the PDF/3-3b with the XML attachment.
Creating a ZUGFeRD Invoice
The example implements the XRechnung class to abstract the data layer from ZUGFeRD-csharp.
public class XRechnung | |
{ | |
public string InvoiceNumber { get; set; } = Guid.NewGuid().ToString(); | |
public DateTime InvoiceDate { get; set; } = DateTime.Now; | |
public CurrencyCodes Currency { get; set; } = CurrencyCodes.EUR; | |
public string OrderNumber { get; set; } | |
public DateTime DeliveryDate { get; set; } | |
public string PaymentTerms { get; set; } = "Zahlbar innerhalb 30 Tagen netto bis 04.07.2023"; | |
public DateTime PaymentDueDate { get; set; } = DateTime.Now.AddDays(30); | |
public Seller Seller { get; set; } | |
public Buyer Buyer { get; set; } | |
public List<LineItem> LineItems { get; set; } = new(); | |
public decimal TotalNetAmount => LineItems.Sum(item => item.LineTotal); | |
public decimal TotalTaxAmount => LineItems.Sum(item => item.TaxAmount); | |
public decimal TotalGrossAmount => LineItems.Sum(item => item.Total); | |
public decimal TotalAllowanceChargeAmount { get; set; } = 0; | |
public decimal TotalChargeAmount { get; set; } = 0; | |
public decimal TotalPrepaidAmount { get; set; } = 0; | |
public decimal TotalRoundingAmount { get; set; } = 0; | |
public decimal TotalDueAmount => TotalGrossAmount - TotalPrepaidAmount + TotalTaxAmount; | |
} |
The CreateXML method creates an XML representation of the XRechnung object using the ZUGFeRD-csharp library and returns this XML.
public string CreateXML() | |
{ | |
// Create a new invoice descriptor with basic details | |
InvoiceDescriptor desc = InvoiceDescriptor.CreateInvoice( | |
this.InvoiceNumber, | |
this.InvoiceDate, | |
this.Currency | |
); | |
// Set buyer details | |
desc.ReferenceOrderNo = this.OrderNumber; | |
desc.SetBuyer(this.Buyer.Name, this.Buyer.ZipCode, this.Buyer.City, this.Buyer.Street, CountryCodes.DE, this.Buyer.Phone); | |
desc.AddBuyerTaxRegistration(this.Buyer.VATID, TaxRegistrationSchemeID.VA); | |
desc.SetBuyerContact(this.Buyer.Contact); | |
desc.SetBuyerOrderReferenceDocument(this.Buyer.OrderReferenceDocument, this.Buyer.OrderReferenceDocumentDate); | |
// Set seller details | |
desc.SetSeller(this.Seller.Name, this.Seller.ZipCode, this.Seller.City, this.Seller.Street, CountryCodes.DE, this.Seller.Phone); | |
desc.AddSellerTaxRegistration(this.Seller.VATID, TaxRegistrationSchemeID.VA); | |
desc.SetSellerContact(this.Seller.Contact, this.Seller.OrganizationUnit, this.Seller.Email, this.Seller.Phone); | |
// Set delivery date | |
desc.ActualDeliveryDate = this.DeliveryDate; | |
// Set invoice totals | |
desc.SetTotals( | |
this.TotalNetAmount, | |
this.TotalChargeAmount, | |
this.TotalAllowanceChargeAmount, | |
this.TotalGrossAmount, | |
this.TotalTaxAmount, | |
this.TotalNetAmount + this.TotalTaxAmount, // Total Amount Including Taxes | |
this.TotalPrepaidAmount, | |
this.TotalDueAmount | |
); | |
// Set payment terms | |
desc.AddTradePaymentTerms(this.PaymentTerms, this.PaymentDueDate); | |
// Add line items to the invoice | |
foreach (LineItem lineItem in this.LineItems) | |
{ | |
desc.AddTradeLineItem( | |
lineItem.Name, | |
lineItem.Description, | |
lineItem.Unit, | |
lineItem.Quantity, | |
lineItem.UnitPrice + (lineItem.UnitPrice * lineItem.TaxPercent / 100), // Gross Unit Price | |
lineItem.UnitPrice, // Net Unit Price | |
lineItem.Quantity, | |
lineItem.LineTotal, | |
lineItem.TaxType, | |
TaxCategoryCodes.S, | |
lineItem.TaxPercent | |
); | |
} | |
// Add applicable trade tax | |
desc.AddApplicableTradeTax(this.TotalNetAmount, 19m, TaxTypes.VAT, TaxCategoryCodes.S); | |
// Set payment means | |
desc.SetPaymentMeans(PaymentMeansTypeCodes.ClearingBetweenPartners); | |
// Write XML to a memory stream | |
using MemoryStream stream = new(); | |
desc.Save(stream, ZUGFeRDVersion.Version23, Profile.XRechnung); | |
// Convert the memory stream content to a string | |
stream.Seek(0, SeekOrigin.Begin); | |
using StreamReader reader = new(stream); | |
return reader.ReadToEnd(); | |
} |
Using sample data, the following code demonstrates how to create a new XRechnung object.
XRechnung xRechnung = new XRechnung() | |
{ | |
InvoiceNumber = "471102", | |
InvoiceDate = new DateTime(2024,12,30), | |
Currency = CurrencyCodes.EUR, | |
OrderNumber = "AB-312", | |
DeliveryDate = new DateTime(2024, 12, 30), | |
PaymentTerms = "Zahlbar innerhalb 30 Tagen netto.", | |
PaymentDueDate = new DateTime(2025,01,15), | |
Seller = new Seller() | |
{ | |
Name = "Lieferant GmbH", | |
Street = "Lieferantenstra??e", | |
ZipCode = "80333", | |
City = "M??nchen", | |
VATID = "DE123456789", | |
TaxRegistrationSchemeID = TaxRegistrationSchemeID.VA, | |
Contact = "Max Mustermann", | |
Phone = "+4942142706710", | |
Email = "max@mustermann.de" | |
}, | |
Buyer = new Buyer() | |
{ | |
Name = "Kunden Mitte AG", | |
Street = "Kundenstra??e", | |
ZipCode = "69876", | |
City = "Frankfurt", | |
VATID = "DE234567890", | |
TaxRegistrationSchemeID = TaxRegistrationSchemeID.VA, | |
Contact = "Hans Muster", | |
OrganizationUnit = "Einkauf", | |
Email = "hans@muster.de", | |
Phone = "22342424", | |
OrderReferenceDocument = "2013-471102", | |
}, | |
LineItems = new List<LineItem>() | |
{ | |
new LineItem() | |
{ | |
Name = "Item name", | |
Description = "Detail description", | |
Quantity = 1, | |
Unit = QuantityCodes.H87, | |
UnitPrice = 10m, | |
TaxCategory = TaxCategoryCodes.S, | |
TaxType = TaxTypes.VAT, | |
TaxPercent = 19, | |
} | |
} | |
}; |
The CreateXML method is then called to generate the XML, which is then used in an Embedded
╰ TXTextControl Namespace
╰ EmbeddedFile Class
The EmbeddedFile class represents a file embedded in another document. object along with the XML metadata.
// Serialize the XRechnung object to JSON | |
string json = JsonSerializer.Serialize(xRechnung); | |
// Generate the ZUGFeRD XML | |
string xmlZugferd = xRechnung.CreateXML(); | |
// Load metadata from the XML file | |
string metaData = File.ReadAllText("metadata.xml"); | |
// Create an embedded file for the ZUGFeRD invoice | |
var zugferdInvoice = new TXTextControl.EmbeddedFile( | |
"factur-x.xml", | |
Encoding.UTF8.GetBytes(xmlZugferd), | |
metaData) | |
{ | |
Description = "factur-x", | |
Relationship = "Alternative", | |
MIMEType = "application/xml", | |
LastModificationDate = DateTime.Now | |
}; | |
// Configure save settings with the embedded file | |
var saveSettings = new TXTextControl.SaveSettings | |
{ | |
EmbeddedFiles = new[] { zugferdInvoice } | |
}; | |
// Create, modify, and save the PDF document | |
using (var tx = new TXTextControl.ServerTextControl()) | |
{ | |
tx.Create(); | |
tx.Text = "Test Document"; | |
tx.Save("test.pdf", TXTextControl.StreamType.AdobePDFA, saveSettings); | |
} |
The required XML metadata includes the RDF description of the embedded structures.
<rdf:Description xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" | |
xmlns:pdfaField="http://www.aiim.org/pdfa/ns/field#" | |
xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" | |
xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" | |
xmlns:pdfaType="http://www.aiim.org/pdfa/ns/type#" | |
rdf:about="" | |
> | |
<pdfaExtension:schemas> | |
<rdf:Bag> | |
<rdf:li rdf:parseType="Resource"> | |
<pdfaSchema:schema>ZUGFeRD PDFA Extension Schema</pdfaSchema:schema> | |
<pdfaSchema:namespaceURI>urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#</pdfaSchema:namespaceURI> | |
<pdfaSchema:prefix>fx</pdfaSchema:prefix> | |
<pdfaSchema:property> | |
<rdf:Seq> | |
<rdf:li rdf:parseType="Resource"> | |
<pdfaProperty:name>DocumentFileName</pdfaProperty:name> | |
<pdfaProperty:valueType>Text</pdfaProperty:valueType> | |
<pdfaProperty:category>external</pdfaProperty:category> | |
<pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description> | |
</rdf:li> | |
<rdf:li rdf:parseType="Resource"> | |
<pdfaProperty:name>DocumentType</pdfaProperty:name> | |
<pdfaProperty:valueType>Text</pdfaProperty:valueType> | |
<pdfaProperty:category>external</pdfaProperty:category> | |
<pdfaProperty:description>INVOICE</pdfaProperty:description> | |
</rdf:li> | |
<rdf:li rdf:parseType="Resource"> | |
<pdfaProperty:name>Version</pdfaProperty:name> | |
<pdfaProperty:valueType>Text</pdfaProperty:valueType> | |
<pdfaProperty:category>external</pdfaProperty:category> | |
<pdfaProperty:description>The actual version of the ZUGFeRD data</pdfaProperty:description> | |
</rdf:li> | |
<rdf:li rdf:parseType="Resource"> | |
<pdfaProperty:name>ConformanceLevel</pdfaProperty:name> | |
<pdfaProperty:valueType>Text</pdfaProperty:valueType> | |
<pdfaProperty:category>external</pdfaProperty:category> | |
<pdfaProperty:description>The conformance level of the ZUGFeRD data</pdfaProperty:description> | |
</rdf:li> | |
</rdf:Seq> | |
</pdfaSchema:property> | |
</rdf:li> | |
</rdf:Bag> | |
</pdfaExtension:schemas> | |
</rdf:Description> | |
<rdf:Description xmlns:fx="urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#" | |
fx:ConformanceLevel="EN 16931" | |
fx:DocumentFileName="factur-x.xml" | |
fx:DocumentType="INVOICE" | |
fx:Version="2p0" | |
rdf:about=""/> |
Creating the PDF
In this example, we will not generate an actual invoice PDF, but only a dummy text, as this should only focus on showing how to embed the XML invoice part.
Learn More
This article shows how to create valid XRechnung and ZUGFeRD invoices with ASP.NET Core C#. The invoice is created with the help MailMerge component and the ZUGFeRD-csharp library.
Creating Valid XRechnung / ZUGFeRD Invoices with ASP.NET Core C#
When you open the PDF in Acrobat Reader, you can see the attachment in the Attachments panel.
Validation
To verify that the document is valid according to the settings, you can now upload the document to any validation service.
Conclusion
Creating ZUGFeRD invoices is a common requirement for many companies. The ZUGFeRD-csharp library provides an easy way to programmatically create the XML content. The TX Text Control library can be used to create the PDF/A-3b file with the embedded XML attachment.
By combining these two libraries, you can create valid ZUGFeRD invoices in C#.
Download the sample from GitHub and test it with your own data.