Create Factur-X, ZUGFeRD and XRechnung Electronic Invoices

Winnovative HTML to PDF Converter creates electronic invoices in the Factur-X and ZUGFeRD formats (including the XRechnung profile) and electronic orders in the Order-X format. An electronic invoice is a PDF/A-3 document that shows the invoice as a page a person reads and carries the same invoice as an XML file that accounting software reads automatically. Factur-X and ZUGFeRD are the French and the German name of the same format.

The invoice XML is loaded in an Winnovative.Pdf.NextElectronicInvoice object with ElectronicInvoiceFromXml(Byte), ElectronicInvoiceFromXml(String) or ElectronicInvoiceFromFile(String). The profile, the document type and the invoice number are taken from the XML and exposed by the Profile, DocumentType and InvoiceNumber properties. The invoice is then added to the generated document with PdfDocumentOptionsAddElectronicInvoice(ElectronicInvoice). The same method is available on PdfDocumentAddElectronicInvoice(ElectronicInvoice) for documents created from scratch and on PdfEditorAddElectronicInvoice(ElectronicInvoice) for existing PDF/A-3 documents. The produced document embeds the invoice XML and carries the invoice identification that the receiving software looks for.

Supported profiles

Invoices in the UBL syntax and ZUGFeRD 1.0 invoices are not supported. The XML is embedded as given.

Requirements

Accessible invoices. With the PdfStandardPdfUa1PdfA3a standard the invoice page is also a PDF/UA-1 document, readable with screen readers and assistive technologies, as required for the invoices delivered to consumers under the European Accessibility Act. The HTML of the invoice should use headings, tables with header cells and alternate text for images.

Setting HtmlToPdfConverterPdfViewerPreferences.PageMode to ViewerPageModeUseAttachments opens the Attachments panel when the document is loaded, so the embedded invoice XML is visible to the reader.

Code Sample - Create a Factur-X / ZUGFeRD Electronic Invoice

C#
using System;
using System.IO;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Winnovative_Next_AspNetDemo.Models;
using Winnovative_Next_AspNetDemo.Models.HTML_to_PDF;

// Use Winnovative Namespace
using Winnovative.Pdf.Next;

namespace Winnovative_Next_AspNetDemo.Controllers.HTML_to_PDF
{
    public class Create_Electronic_Invoice_PDFController : Controller
    {
        private readonly IWebHostEnvironment m_hostingEnvironment;
        public Create_Electronic_Invoice_PDFController(IWebHostEnvironment hostingEnvironment)
        {
            m_hostingEnvironment = hostingEnvironment;
        }

        public IActionResult Index()
        {
            var model = SetViewModel();
            return View(model);
        }

        [HttpPost]
        public async Task<ActionResult> ConvertHtmlToPdf(Create_Electronic_Invoice_PDF_ViewModel model)
        {
            if (!ModelState.IsValid)
            {
                var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
                throw new ValidationException(errorMessage);
            }

            // Set the license key received after purchase to use the library in licensed mode; leave it commented for demo mode
            // Licensing.LicenseKey = "your-license-key";

            // The invoice XML: the uploaded file when there is one, otherwise the selected sample
            byte[] invoiceXml = await ReadInvoiceXml(model);

            // Read the invoice. The profile and the document type (invoice, credit note, order...) are
            // taken from the XML itself
            ElectronicInvoice invoice = ElectronicInvoice.FromXml(invoiceXml);

            // How the embedded XML relates to the visible page. MINIMUM and BASIC WL do not carry
            // the whole invoice, so for them the relationship is always Data
            invoice.Relationship = model.Relationship;

            // Create a HTML to PDF converter object with default settings
            HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();

            // An electronic invoice needs a PDF/A-3 carrier. PDF/UA-1 + PDF/A-3a produces an
            // invoice that is also accessible
            htmlToPdfConverter.PdfDocumentOptions.PdfStandard = model.PdfStandard;

            // Embed the invoice XML and write the metadata invoice software looks for
            htmlToPdfConverter.PdfDocumentOptions.AddElectronicInvoice(invoice);

            // Open the Attachments panel on document load, so the embedded XML is visible
            htmlToPdfConverter.PdfViewerPreferences.PageMode = ViewerPageMode.UseAttachments;

            byte[] outPdfBuffer = htmlToPdfConverter.ConvertHtml(model.HtmlString, model.BaseUrl);

            // Send the PDF file to browser
            FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
            fileResult.FileDownloadName = string.Format("{0}_{1}_{2}.pdf", invoice.DocumentType, invoice.InvoiceNumber, invoice.Profile);

            return fileResult;
        }

        private async Task<byte[]> ReadInvoiceXml(Create_Electronic_Invoice_PDF_ViewModel model)
        {
            if (model.InvoiceXmlFile != null && model.InvoiceXmlFile.Length > 0)
            {
                using var ms = new MemoryStream();
                await model.InvoiceXmlFile.CopyToAsync(ms);
                return ms.ToArray();
            }

            // Only file names from the samples folder are accepted
            string fileName = Path.GetFileName(model.SampleInvoice ?? string.Empty);
            string filePath = Path.Combine(GetInvoiceFilesPath(), fileName);
            if (fileName.Length == 0 || !System.IO.File.Exists(filePath))
                throw new Exception("No invoice XML provided: upload a file or select a sample");

            return await System.IO.File.ReadAllBytesAsync(filePath);
        }

        private Create_Electronic_Invoice_PDF_ViewModel SetViewModel()
        {
            var model = new Create_Electronic_Invoice_PDF_ViewModel();

            var contentRootPath = Path.Combine(m_hostingEnvironment.ContentRootPath, "wwwroot");

            HttpRequest request = ControllerContext.HttpContext.Request;
            UriBuilder uriBuilder = new UriBuilder();
            uriBuilder.Scheme = request.Scheme;
            uriBuilder.Host = request.Host.Host;
            if (request.Host.Port != null)
                uriBuilder.Port = (int)request.Host.Port;
            uriBuilder.Path = request.PathBase.ToString() + request.Path.ToString();
            uriBuilder.Query = request.QueryString.ToString();

            string currentPageUrl = uriBuilder.Uri.AbsoluteUri;
            string rootUrl = currentPageUrl.Substring(
                0, currentPageUrl.Length - "Create_Electronic_Invoice_PDF".Length);

            model.HtmlString = System.IO.File.ReadAllText(Path.Combine(contentRootPath, "DemoAppFiles/Input/HTML_Files/Electronic_Invoice.html"));
            model.BaseUrl = rootUrl + "DemoAppFiles/Input/HTML_Files/";

            return model;
        }

        private string GetDemoFilesPath() => m_hostingEnvironment.ContentRootPath + "/wwwroot" + "/DemoAppFiles/Input/";
        private string GetInvoiceFilesPath() => Path.Combine(GetDemoFilesPath(), "Invoice_Files");
    }
}

See Also