Available Now: Explore our latest release with enhanced accessibility and powerful IDP features
By Roger Dunham | 2024 Nov 06
4 min
Tags
develop pdf
Summary: Apryse offers tools to help businesses meet EU electronic invoicing standards, like EN16931, using compliant formats such as ZUGFeRD and Factur-X. These hybrid formats combine readable PDFs and machine-readable XML, ensuring seamless compliance with country-specific mandates in Germany, France, and beyond.
If you send electronic invoices within the EU, then you probably already know about EN16931. It’s the standard that specifies what an invoice must contain for it to comply with EU directive 2014/55/EU (which relates to Electronic Invoicing in Public Procurement). However, electronic invoices will soon become mandatory in some countries for almost all invoices.
In Germany, electronic invoices will be mandatory for all B2B transactions starting 2028, but the transitional rules come into force earlier - from 1st January 2025.
In France, the mandatory date for e-invoicing and e-reporting in is September 1, 2026 (for large and intermediate-sized taxpayers), and September 1, 2027, for small and medium-sized businesses. All companies must, however, be able to receive electronic invoices by September 1, 2026.
Thankfully, Apryse SDK have technology to help you out.
Both ZUGFeRD and Factur-X are a hybrid electronic invoicing format that combines both a human-readable PDF and machine-readable XML in a single file. This makes the invoice accessible for human viewing and automated processing, providing support for both traditional invoicing workflows and newer digital ones.
You may be wondering what the difference is between the two invoice types? In reality, nothing! ZUGFeRD was developed in Germany, while Factur-X was developed, in parallel, in France. In recent years the two standards have been fully harmonized, but there is no plan to drop either of the names, so while the content is essentially the same, the name used will depend on where you are.
Read a great introduction to ZUGFeRD and Factur-X.
Let’s look at a ZUGFeRD invoice that was generated by iText. We’ll do that using Xodo PDF Studio.
Figure 1 - An example ZUGFeRD invoice shown in Xodo PDF Studio.
There are four things to note here:
Read more about leveraging PDF/A for compliance and preservation.
Let’s create a ZUGFeRD 2.1 invoice (Yes, I know that there are later versions). There are subtle differences if you use another version of the ZUGFeRD standard (or Factur-X), but the principles are the same, and you can extend what you learn here to work with those.
We will use C# in this example, but the Apryse SDK supports many other languages including C++, Java, and JavaScript (Node.js).
PDFNet is the main gateway to the Apryse SDK. Before the SDK can be used, we need to initialize PDFNet, passing in a license key. It’s free to get a trial license key.
We also need to specify the source documents. We are going to use a PDF that shows the human readable version of the invoice, called invoice-raw.pdf and the associated XML version of the invoice called zugferd-invoice.xml.
Note that the name of the XML file matters - it needs to follow that used in the specification for the version of ZUGFeRD that you are using. For ZUGFeRD 1.0 the file name must be “ZUGFeRD-invoice.xml”; for ZUGFeRD 2.0 the file name must be “zugferd-invoice.xml” and for ZUGFeRD 2.1 and later, and for Factur-X, the file name must be “factur-x.xml”
Finally, we need to set the ColorManagement (else we may get PDF/A validation errors later in the process).
PDFNet.Initialize([License key]);
PDFNet.SetColorManagement(PDFNet.CMSType.e_lcms); // Required for PDFA validation.
string pathToFile = input_path + "invoice-raw.pdf";
// The invoice name must match that needed for the version of the ZUGFeRD standard
// In this case we are creating ZUGFeRD 2.1
string invoiceXMLName = "factur-x.xml";
In order to embed the invoice into the PDF we will create a new PDFDoc from the original PDF.
We then add a name tree called EmbeddedFiles to the PDFDoc. A name tree is similar to a dictionary, and we use it to store the file contents (using a FileSpec object), the name of and some other information about the XML invoice file.
This is all done using low level PDF object processing code which allows you fine grain manipulation of the PDF structure.
The PDFDoc is then saved into an array of bytes which we use in the next step.
// 1) Read a PDF document and XML invoice from disk
// 2) Embed the invoice into the PDF
// 3) Save the result as a new, in-memory PDF file (stored in a byte array)
byte[] doc_bytes_invoice_added = null;
using (PDFDoc in_doc = new PDFDoc(pathToFile))
{
in_doc.InitSecurityHandler();
NameTree files = NameTree.Create(in_doc, "EmbeddedFiles");
FileSpec fs = FileSpec.Create(in_doc, input_path + invoiceXMLName, true);
byte[] file1_name = System.Text.Encoding.UTF8.GetBytes("factur-x.xml");
files.Put(file1_name, fs.GetSDFObj());
fs.GetSDFObj().PutText("Desc", "ZUGFeRD Rechnung");
fs.GetSDFObj().PutName("AFRelationship", "Alternative");
fs.GetSDFObj().PutText("F", "factur-x.xml");
fs.GetSDFObj().PutText("UF", "factur-x.xml");
doc_bytes_invoice_added = in_doc.Save(SDFDoc.SaveOptions.e_incremental);
}
We saw earlier that one of the requirements of ZUGFeRD is that the PDF must be a PDF/A-3.
We can convert the byte array that was created at the end of the previous step into PDF/A using a PDFACompliance object.
In this case we are confident that the PDF will be compliant, but if that wasn’t the case then you would probably want to check for any validation errors.
// Convert the PDF created above to PDF/A
// Store the results in a new byte array
byte[] doc_bytes_pdfa = null;
using (PDFACompliance pdf_a = new PDFACompliance(true, doc_bytes_invoice_added, null, PDFACompliance.Conformance.e_Level3B, null, 10, false))
{
doc_bytes_pdfa = pdf_a.SaveAs(false);
}
The ZUGFeRD process requires some metadata in the PDF to allow the invoice to be extracted, validated and processed. Furthermore, this metadata needs to follow the Cross Industry Invoice (CII) standard, which is compliant with EN 16931.
Thankfully though, it’s easy to set up, and doesn’t change, even if it is non-trivial to understand. The main parts are that there is a ZUGFeRD invoice, with information about the conformance level and file names.
Note that the use of the ‘zf’ namespace is deprecated in the ZUGFeRD 2.1, and we are therefore using ‘fx’. If you need to create an earlier ZUGFeRD version, then you will need to modify the namespace.
The code therefore reads the metadata into a string called zugferd_xmp, which we will use in a minute.
<rdf:Description
xmlns:fx="urn:ferd:pdfa:CrossIndustryDocument:invoice:2p0#" rdf:about="">
<fx:ConformanceLevel>BASIC</fx:ConformanceLevel>
<fx:DocumentFileName>factur-x.xml</fx:DocumentFileName>
<fx:DocumentType>INVOICE</fx:DocumentType>
<fx:Version>1.0</fx:Version>
</rdf:Description>
<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>ZUGFeRD PDFA Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:ferd: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 ZUGFeRD 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 ZUGFeRD data</pdfaProperty:description>
</rdf:li>
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
Next the existing metadata is extracted from the PDF as a stream xmp_stm, which is decoded and converted into the string xmp_str. The zugferd_xmp data is inserted into that string, and the combined string is converted back into a byte stream which is used to replace the metadata in the PDF.
// Add XMP metadata to describe the embedded invoice as a ZUGFeRD invoice
using (PDFDoc in_doc = new PDFDoc(doc_bytes_pdfa, doc_bytes_pdfa.Length))
{
in_doc.InitSecurityHandler();
Obj xmp_stm = in_doc.GetRoot().FindObj("Metadata");
Filter stm = xmp_stm.GetDecodedStream();
FilterReader reader = new FilterReader(stm);
byte[] buf = new byte[stm.Size()];
int nb = reader.Read(buf);
String xmp_str = System.Text.ASCIIEncoding.ASCII.GetString(buf);
var insertPos = xmp_str.IndexOf("<rdf:Description ");
xmp_str = xmp_str.Insert(insertPos, zugferd_xmp);
xmp_stm.SetStreamData(System.Text.Encoding.ASCII.GetBytes(xmp_str));
// mode code...
That’s all the hard work done. All that is needed is to save the PDFDoc with its final name (in this case zugferd_invoice2.1.pdf.
//Save the final file
in_doc.Save(output_path + "zugferd_invoice2.1.pdf", SDFDoc.SaveOptions.e_linearized);
}
That’s it! You can now take your ZUGFeRD Invoice, and, if you wish, validate it using one of the available validators such as Mustang.
Figure 2 - Output from validating the generated PDF using Mustang
You should now have a working prototype able to automatically generate ZUGFeRD-valid (or with a few minor tweaks Factur-X valid) invoices using Apryse's SDK.
Next, you may want to add other invoicing capabilities to your solution or use the SDK to perform a host of other document processing tasks.
If you have any questions about Apryse’s SDK, please feel free to reach out to us on Discord channel.
Tags
develop pdf
Roger Dunham
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content