Available Now: Explore our latest release with enhanced accessibility and powerful IDP features

How to Create ZugFeRD and Factur-X compliant Invoices with Apryse

By Roger Dunham | 2024 Nov 06

Sanity Image
Read time

4 min

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.

Introduction

Copied to clipboard

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.

What are ZUGFeRD or a Factur-X Invoices?

Copied to clipboard

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.

What are the main parts of a ZUGFeRD or a Factur-X Invoice?

Copied to clipboard

Let’s look at a ZUGFeRD invoice that was generated by iText. We’ll do that using Xodo PDF Studio.

Blog image

Figure 1 - An example ZUGFeRD invoice shown in Xodo PDF Studio.

There are four things to note here:

  1. The original PDF in a human readable format
  2. There is an attachment (which is machine readable XML)
  3. There is metadata embedded in the PDF
  4. There is a message indicating that the PDF is PDF/A-3. Being PDF/A means that the PDF will have the same appearance even if a user doesn’t have the same fonts, and PDF/A-3 is the earliest version that allowed arbitrary files to be embedded.

Creating a ZUGFeRD invoice

Copied to clipboard

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).

 Step 1: Initialize PDFNet

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";

Step 2: Embed the XML file into the PDF

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);

}

Step 3: Convert the PDF into PDF/A-3B

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);

}

Step 4: Create a string containing ZUGFeRD specific Metadata

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>

Step 5: Write the ZUGFeRD specific metadata into the existing metadata for the PDF.

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...

Step 6: Save the File

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.

Blog image

Figure 2 - Output from validating the generated PDF using Mustang

Use Other PDF Invoicing Features

Copied to clipboard

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.

Sanity Image

Roger Dunham

Share this post

email
linkedIn
twitter