SelectPdf for .NET 10 - Electronic Invoice (ZUGFeRD / Factur-X) - C# / .NET 10 Sample

A hybrid electronic invoice carries the same invoice twice: the page a person reads, and the xml a machine reads, embedded in the same pdf. ZUGFeRD (Germany) and Factur-X (France) are the same standard under two names.

AddZugferdInvoice embeds the invoice xml and writes the metadata invoice software looks for, in one call. The carrier must be a PDF/A-3 document, which is why the sample converts the invoice page with PdfStandard.PdfA3B - a document that is not PDF/A-3 is refused rather than producing a file no invoice software would accept.

The name of the embedded file is fixed by the standard (factur-x.xml, or xrechnung.xml for the XRECHNUNG profile) and is derived from the profile rather than taken from you, so the name in the pdf and the name in the metadata cannot drift apart.

The invoice page rendered here is this html invoice, and the xml embedded in it is this Factur-X xml. Producing that xml is your application's job - SelectPdf embeds and identifies it.

Invoice profile (must describe the xml you embed):


Relationship to the visible invoice:


Sample code · C#
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using SelectPdf.Universal;

namespace SelectPdf.Samples.Pages
{
    public class ElectronicInvoiceModel : PageModel
    {
        public void OnGet()
        {
            DdlProfile = "En16931";
            DdlRelationship = "Alternative";
        }

        [BindProperty]
        public string DdlProfile { get; set; } = "En16931";

        [BindProperty]
        public string DdlRelationship { get; set; } = "Alternative";

        public List<SelectListItem> Profiles { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "Minimum", Text = "MINIMUM - accounting information only" },
            new SelectListItem { Value = "BasicWL", Text = "BASIC WL - header/footer data, no lines" },
            new SelectListItem { Value = "Basic", Text = "BASIC - simple invoices, with lines" },
            new SelectListItem { Value = "En16931", Text = "EN 16931 - the full European standard" },
            new SelectListItem { Value = "Extended", Text = "EXTENDED - EN 16931 plus extra terms" },
            new SelectListItem { Value = "XRechnung", Text = "XRECHNUNG - German public sector" },
        };

        public List<SelectListItem> Relationships { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "Alternative", Text = "Alternative - same content as the page" },
            new SelectListItem { Value = "Data", Text = "Data - the page shows more than the xml" },
            new SelectListItem { Value = "Source", Text = "Source - the page was generated from the xml" },
        };

        public IActionResult OnPost()
        {
            string invoiceHtml = Path.Combine(Startup.ServerPath, "wwwroot", "files", "invoice.html");
            string invoiceXml = Path.Combine(Startup.ServerPath, "wwwroot", "files", "factur-x.xml");

            // the carrier: the invoice page, converted as a PDF/A-3 document
            HtmlToPdf converter = new HtmlToPdf();
            converter.Options.NavigationTimeout = Startup.NavigationTimeout;
            converter.Options.PdfStandard = PdfStandard.PdfA3B;

            PdfDocument doc = converter.ConvertHtmlString(
                System.IO.File.ReadAllText(invoiceHtml), string.Empty);

            // the profile says how much of the EN 16931 model the xml carries, and is recorded in
            // the invoice metadata - it must describe the xml actually embedded
            ZugferdProfile profile =
                SampleForm.ParseEnum(DdlProfile, ZugferdProfile.En16931);

            // Alternative means the xml and the visible page carry the same invoice content, which
            // is what Germany requires for the BASIC, EN 16931, EXTENDED and XRECHNUNG profiles;
            // Data says the page shows more than the xml does
            PdfAttachmentRelationship relationship =
                SampleForm.ParseEnum(DdlRelationship, PdfAttachmentRelationship.Alternative);

            // embed the invoice xml and mark the document as an electronic invoice
            using (FileStream xml = System.IO.File.OpenRead(invoiceXml))
            {
                doc.AddZugferdInvoice(xml, profile, relationship);
            }

            // save pdf document
            byte[] pdf = doc.Save();

            // close pdf document
            doc.Close();

            // return resulted pdf document
            FileResult fileResult = new FileContentResult(pdf, "application/pdf");
            fileResult.FileDownloadName = "Invoice.pdf";
            return fileResult;
        }
    }
}