Creating ZUGFeRD Invoices with PDFEdit
Requirements: Secure PDF and IPWorks (optional)
Introduction
ZUGFeRD is a standardized electronic invoicing format developed to simplify the exchange of invoices between businesses and public administrations. It combines a human-readable PDF/A-3 invoice with a machine-readable XML file embedded within the PDF, enabling seamless automation, validation, and archiving of invoice data. This hybrid approach ensures compliance with EU standards while maintaining visual compatibility with traditional invoice processing.
Using the PDFEdit component from Secure PDF, this integration of XML data into a PDF to create a ZUGFeRD-compliant invoice can be achieved efficiently and programmatically.
ZUGFeRD and Factur-X
ZUGFeRD and Factur-X are both hybrid invoice formats that combine a human-readable PDF with embedded machine-readable XML data. Although ZUGFeRD originated in Germany and Factur-X in France, both formats have been harmonized and now share a common technical foundation. Starting with ZUGFeRD version 2.1, the standard is fully aligned with Factur-X, meaning any invoice conforming to ZUGFeRD 2.1 or later is also Factur-X compliant. The PDFEdit component does not enforce a specific ZUGFeRD version when generating invoices—it can produce valid ZUGFeRD invoices based on the input PDF and XML data.
ZUGFeRD Format Requirements
The following pieces are required for ZUGFeRD invoices:
-
PDF/A-3 Format
The PDF, serving as the human-readable part of the hybrid invoice and container for the XML, must conform to PDF/A-3 (a, b, or u). Additional attachments (e.g., images or spreadsheets) are allowed. -
Single XML Attachment
Only one XML invoice is allowed, associated at document level using the AFRelationship key in the file specification dictionary. In the latest versions of ZUGFeRD the filename of the attachment must be factur-x.xml. This XML document is the machine-readable part of the hybrid invoice. -
XMP Metadata
The PDF must include XMP metadata containing key invoice details as well as PDF/A information, which we will cover in more detail in the next section.
XMP Metadata
To comply with the ZUGFeRD specification, a PDF invoice must include XMP metadata, a standardized form of embedded metadata that describes the attached XML invoice and the PDF/A-3 conformance. This metadata does not contain the invoice data itself, but provides descriptive information about the embedded XML file, such as the document type, filename, schema version, and profile (e.g., BASIC, COMFORT, EN 16931).
Below is an example of the XMP metadata structure used for ZUGFeRD 2.1 and later.
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about="">
<pdf:Producer>ProducerName</pdf:Producer>
</rdf:Description>
<rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="">
<pdfaid:conformance></pdfaid:conformance>
<pdfaid:part>3</pdfaid:part>
</rdf:Description>
<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about="">
<dc:format>application/pdf</dc:format>
<dc:creator>
<rdf:Seq>
<rdf:li></rdf:li>
</rdf:Seq>
</dc:creator>
<dc:date>
<rdf:Seq>
<rdf:li></rdf:li>
</rdf:Seq>
</dc:date>
</rdf:Description>
<!-- The actual Factur-X properties -->
<rdf:Description xmlns:fx="urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#" rdf:about="">
<fx:ConformanceLevel></fx:ConformanceLevel>
<fx:DocumentType>INVOICE</fx:DocumentType>
<fx:DocumentFileName></fx:DocumentFileName>
<fx:Version>1.0</fx:Version>
</rdf:Description>
<!-- PDF/A extension schema description for the Factur-X schema -->
<rdf:Description
xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" rdf:about="">
<pdfaExtension:schemas>
<rdf:Bag>
<rdf:li rdf:parseType="Resource">
<pdfaSchema:schema>Factur-X PDF/A Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</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 Factur-X XML schema</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 embedded Factur-X data</pdfaProperty:description>
</rdf:li>
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta><?xpacket end="w"?>
The sample code provided below demonstrates how to construct this XMP metadata by starting from a base XML structure and replacing specific parts with values extracted from the actual invoice XML. This can be done using an XML parser, such as the XML component from IPWorks, to read and modify the necessary elements before embedding the result into the PDF.
private string CreateXMPMetadata(string xmlInvoice, char pdfaConformance)
{
XML xmlparser = new XML();
string xmpTemplate = "XMPTemplate.xml";
// Parse the invoice XML
xmlparser.InputFile = xmlInvoice;
xmlparser.Parse();
// Extracting creator's name (the seller's company name)
xmlparser.XPath = "/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:Name";
string creator = xmlparser.XText;
// Current time in the right format
string formattedTime = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:sszzz");
// Extracting the conformance level
xmlparser.XPath = "/rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID";
int lastColonIndex = xmlparser.XText.LastIndexOf(':');
string conformanceLevel = xmlparser.XText.Substring(lastColonIndex + 1).ToUpper();
conformanceLevel = conformanceLevel == "XRECHNUNG_3.0" ? "XRECHNUNG" :
conformanceLevel == "2017" ? "EN 16931" :
conformanceLevel == "BASICWL" ? "BASIC WL" :
conformanceLevel;
xmlparser.Reset();
xmlparser.InputFile = xmpTemplate;
xmlparser.Config("ProcessNamespace=false");
xmlparser.Config("IncludeXMLDeclaration=false");
xmlparser.Config("PreserveAll=true");
xmlparser.Parse();
// Add creator
xmlparser.XPath = "/x:xmpmeta/rdf:RDF/rdf:Description[3]/dc:creator/rdf:Seq/rdf:li";
xmlparser.XText = creator;
// Add current time
xmlparser.XPath = "/x:xmpmeta/rdf:RDF/rdf:Description[3]/dc:date/rdf:Seq/rdf:li";
xmlparser.XText = formattedTime;
// Add conformance level (profile)
xmlparser.XPath = "/x:xmpmeta/rdf:RDF/rdf:Description[4]/fx:ConformanceLevel";
xmlparser.XText = conformanceLevel;
// Add XML filename
xmlparser.XPath = "/x:xmpmeta/rdf:RDF/rdf:Description[4]/fx:DocumentFileName";
xmlparser.XText = Path.GetFileName(xmlInvoice);
// Add PDF/A conformance
xmlparser.XPath = "/x:xmpmeta/rdf:RDF/rdf:Description[2]/pdfaid:conformance";
xmlparser.XText = pdfaConformance.ToString();
xmlparser.Save();
return xmlparser.OutputData;
}
Generating a ZUGFeRD Invoice
The code below demonstrates how to create a fully compliant ZUGFeRD invoice using the PDFEdit component. First, it attaches the XML invoice as an embedded file by adding it to the Attachments collection and configures the appropriate AFRelationship to define the relationship between the XML and the PDF content. The XMP metadata, which is generated dynamically from the invoice data as shown in the previous section, is then embedded into the PDF using the SetDocumentProperty method. Finally, it saves the result as a valid PDF/A-3 file that combines both the human-readable PDF and machine-readable XML, fulfilling the core requirement of the ZUGFeRD and Factur-X specifications.
PDFEdit pdfedit = new PDFEdit();
pdfedit.InputFile = "pdfa3b.pdf";
pdfedit.OutputFile = "ZUGFeRD_invoice.pdf";
pdfedit.Overwrite = true;
pdfedit.Open();
// Attaching the XML invoice
PDFAttachment zugferdXML = new PDFAttachment();
zugferdXML.FileName = "C:/ZUGFeRD/factur-x.xml";
zugferdXML.Name = "factur-x.xml";
zugferdXML.Description = "Invoice metadata conforming to ZUGFeRD standard";
zugferdXML.ContentType = "text/xml"; // in the latest version of ZUGFeRD (2.3+) the content type should be text/xml
pdfedit.Attachments.Add(zugferdXML);
// Setting the AFRelationship key depending on the profile
pdfedit.Config("AFRelationship[factur-x.xml]=Data");
string metadata = CreateXMPMetadata("C:/ZUGFeRD/factur-x.xml", 'B');
pdfedit.SetDocumentProperty("Xmp", metadata);
pdfedit.Close();
Once the XML invoice has been attached and the XMP metadata embedded, the resulting PDF can be validated for ZUGFeRD compliance using external tools if desired. Validation support is also planned for a future version of the component.
Conclusion
Creating ZUGFeRD invoices is a common requirement for many companies and is becoming increasingly popular as electronic invoicing standards continue to evolve across Europe. Using the PDFEdit and XML components, this process can be fully automated, ensuring your invoices are both human-readable and machine-processable. By following the steps outlined in this article, you can generate valid ZUGFeRD or Factur-X invoices that meet modern e-invoicing standards and are ready for exchange and archiving.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.