Technisch artikel

ZUGFeRD en Factur-X e-facturen in Delphi met HotPDF

HotPDF genereert, detecteert en valideer elektronische ZUGFeRD- en Factur-X-facturen vanuit Delphi en C++Builder. AddFacturXAssociatedFile bedt de EN 16931 factuur-XML in binnen een PDF/A-3-container, DetectFacturXInvoice herkent een binnenkomende hybride factuur, en HPDFValidateEInvoice voert de officiële EN 16931 Schematron-bedrijfsregels uit op de ingebedde XML. Dit artikel doorloopt alle drie de stappen en is eerlijk over waar de validatie-engine stopt

De druk achter deze functieset is wettelijk en niet technisch. Duitsland en Frankrijk voeren verplichte gestructureerde e-facturatie in voor binnenlandse B2B-transacties, en beide mandaten convergeren naar hetzelfde hybride model: de PDF-factuur die uw ERP al produceert, moet een machineleesbare XML-tegenhanger bevatten. Een Delphi-boekhoud- of ERP-applicatie die vandaag de dag gewone PDF-facturen verzendt, heeft een harde deadline om te beginnen met het verzenden van Factur-X of ZUGFeRD, en het verschil tussen de twee namen is kleiner dan de marketing suggereert — Factur-X en ZUGFeRD zijn dezelfde Duits-Franse standaard die onder twee labels is gepubliceerd

Wat maakt een PDF een geldige Factur-X-factuur?

Een geldige Factur-X-factuur is een PDF/A-3-bestand (ISO 19005-3) dat precies één gestructureerde factuur-XML bevat als gekoppeld bestand, deze linkt vanuit de documentcatalogus en deze declareert in XMP-extensiemetadata. Drie lagen moeten met elkaar overeenstemmen. Ten eerste moet de container voldoen aan PDF/A-3, het archiefprofiel dat — in tegenstelling tot PDF/A-1 en PDF/A-2 — ingebedde bestanden van elk type toestaat. Ten tweede moet de factuur-XML, genaamd factur-x.xml voor standaardprofielen of xrechnung.xml voor de Duitse XRechnung-variant, worden geregistreerd in de catalogusarray /AF en de naamboom /Names /EmbeddedFiles, met een vermelding AFRelationship die aangeeft wat de bijlage is: Alternative betekent dat de XML een gelijkwaardige weergave is van de zichtbare factuur, wat de semantiek is die de meeste profielen vereisen. Ten derde moet het Factur-X XMP-extensieschema het ingebedde bestand, het conformiteitsniveau en het documenttype noemen, zodat een ontvangend systeem de factuur kan classificeren zonder de XML überhaupt te analyseren

De XML-payload zelf volgt het semantische model EN 16931, de Europese norm die de kernfactuur definieert: koper, verkoper, regelitems, btw-specificatie, te betalen bedrag, elk met een zakelijk termnummer (BT-1 voor het factuurnummer, BT-5 voor de valuta, enzovoort). EN 16931-validatie vindt daarom plaats op twee verschillende niveaus. XML-schemavalidatie controleert of het document een correct gevormde CII is met de juiste elementnamen. De Schematron-bedrijfsregels controleren of de inhoud coherent is — dat BR-CO-15 standhoudt en het factuurtotaal daadwerkelijk gelijk is aan de som van de regelnetto-bedragen plus btw, en dat verplichte termen aanwezig zijn voor het gedeclareerde profiel. Een bestand kan slagen voor de schemavalidatie en toch een ongeldige factuur zijn, en dat is waarom het niveau met bedrijfsregels bestaat

Waarom ZUGFeRD 2.5 geen nieuwe container nodig heeft

ZUGFeRD 2.5 verandert niets op het niveau van de PDF-container: de richtlijn-URN's, het XMP-schema en het versietoken zijn identiek aan ZUGFeRD 2.3 / Factur-X 1.0. Dit verrast de meeste implementeerders, dus het is de moeite waard om de bron te citeren. De Factur-X 1.09-specificatie stelt dat het versienummer in de URI van het PDF/A-extensieschema geen verband houdt met het versienummer van de XML-gegevensspecificatie, en dat de versieversie van Factur-X-factuurinstanties 1.0 blijft — zowel in XMP fx:Version en in BT-24 van factur-x.xml. Het officiële ZUGFeRD 2.5-voorbeeldcorpus bevestigt dit: elk voorbeeld sluit factur-x.xml in onder een URN van de richtlijn urn:factur-x.eu:1p0 met een fx:Version van 1.0. Wat 2.5 daadwerkelijk toevoegt, bevindt zich binnen het XML-gegevensmodel — nieuwe elementen zoals subfactuurregels en batch-identificatoren — wat de businesslaag is die uw applicatie genereert, en niet de containerlaag die de PDF-bibliotheek beheert

HotPDF codeert deze bevinding rechtstreeks. AddFacturXAssociatedFile accepteert een argument Version en normaliseert dit via HPDFFacturXNormalizeVersion voordat XMP wordt geschreven, zodat een aanroeper die de alias 2p5 doorgeeft toch het token dat aan de specificatie voldoet genereert, in plaats van een 2p5 XMP-versie te bedenken die geen enkele validator zou accepteren. De detectiehelpers behandelen 2p5 als gelijkwaardig bij het lezen, zodat de heen-en-weer-bewerkingen symmetrisch blijven. Als een bibliotheek of tool u vertelt dat deze een speciale ZUGFeRD 2.5-container schrijft, stelt de specificatie zelf dat zoiets niet bestaat

De factuur-XML inbedden vanuit Delphi

THotPDF.AddFacturXAssociatedFile verandert een reguliere PDF/A-3-generatie in één aanroep in een Factur-X-factuur: het bedt de XML-bytes in als een gekoppeld bestand, registreert factur-x.xml in de catalogusarray /AF en de naamboom /Names /EmbeddedFiles, en genereert de bijbehorende Factur-X XMP-extensiemetadata. U tekent de voor mensen leesbare factuurpagina met de normale HotPDF-canvas-API; het containerwerk is een enkele regel code tussen BeginDoc en EndDoc

var
  Pdf: THotPDF;
  InvoiceXML, UBLXML: TBytes;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-2026-0042.pdf';
    Pdf.PDFACompliance := '3B';            // PDF/A-3b container
    Pdf.BeginDoc;
    // embed the EN 16931 CII invoice XML as factur-x.xml
    InvoiceXML := BuildInvoiceXML;         // produced by your business layer
    Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
    // optional: attach a UBL view as a supplementary representation
    UBLXML := BuildUBLXML;
    Pdf.AddUBLSupplementaryFile(UBLXML);   // factur-xubl.xml, Alternative
    // ... draw the visible invoice page here ...
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

De standaardinstellingen volgen de specificatie: bestandsnaam factur-x.xml, relatie Alternative, versie 1.0. AddUBLSupplementaryFile implementeert sectie 6.4 van de Factur-X 1.09-specificatie, die toestaat dat een UBL-weergave van dezelfde factuur meereist als een second bijlage genaamd factur-xubl.xml met MIME-type application/xml en de relatie Alternative. De volgorde is hier belangrijk, en HotPDF handhaaft de beperking die de specificatie oplegt: het UBL-bestand is een aanvullende weergave, nooit de primaire factuur, dus het moet worden toegevoegd nadat AddFacturXAssociatedFile de CII-master heeft geregistreerd. Let op dat de getekende pagina en de ingebedde XML dezelfde factuur moeten vermelden — een PDF die het ene totaal toont en het andere codeert is precies de foutmodus die hybride facturering probeert te voorkomen. De PDF/A-3-kant van deze workflow, inclusief OutputIntent en lettertype-inbedding, wordt uitgebreider behandeld in het bijbehorende artikel over PDF/A-, PDF/X- en PDF/UA-validatie in Delphi

Hoe detecteert u een Factur-X-factuur in een binnenkomende PDF?

THotPDF.DetectFacturXInvoice beantwoordt de ontvangende kant van het mandaat: laad een willekeurige PDF en ontdek in één aanroep of het een hybride factuur is, welk profiel deze declareert en welke bijlagen deze bevat. De functie retourneert een record THPDFFacturXInvoiceInfo met de ingebedde XML-bestandsnaam, de XMP-gedeclareerde bestandsnaam en het type van het document, de versietoken, het conformiteitsniveau, de richtlijn-URN en de AFRelationship-waarde. Twee verdere velden rapporteren aanvullende weergaven: UBLFileName wordt gevuld wanneer een factur-xubl.xml-bijlage aanwezig is, en EDIFACTFileName wanneer een EDIFACT-weergave meereist — geen van beide heeft invloed op het detectie-oordeel, omdat een aanvullende weergave niet kan bestaan zonder de primaire CII-factuur

var
  Reader: THotPDF;
  Info: THPDFFacturXInvoiceInfo;
begin
  Reader := THotPDF.Create(nil);
  try
    if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
      raise Exception.Create('No pages loaded.');
    if Reader.DetectFacturXInvoice(Info) then
    begin
      Writeln('Invoice XML:    ', string(Info.FileName));
      Writeln('Conformance:    ', string(Info.ConformanceLevel));
      Writeln('Guideline URN:  ', string(Info.GuidelineID));
      Writeln('Relationship:   ', string(Info.AFRelationship));
      if Info.UBLFileName <> '' then
        Writeln('UBL view:       ', string(Info.UBLFileName));
    end
    else
      Writeln('Not a Factur-X / ZUGFeRD hybrid invoice.');
  finally
    Reader.Free;
  end;
end;

Detectie leest dezelfde signalen die een compatibele validator leest — het Factur-X XMP-extensieschema en het register van gekoppelde bestanden — in plaats van alleen te raden op basis van bestandsnamen. Hetzefde geladen-documentobject stelt ook de metadata van de XMP en het Info-woordenboek beschikbaar voor inspectie of correctie, een workflow die is beschreven in het artikel over het bewerken van metadata in geladen PDF-documenten

Bedrijfsregels of schema: wat betekent EN 16931-validatie?

HPDFValidateEInvoice, een op zichzelf staande functie in de unit HPDFEInvoiceValidator, voert beide validatielagen achter elkaar uit: eerst de container-structuurcontrole via ValidateFacturXInvoice, en daarna de EN 16931 Schematron-bedrijfsregels op de geëxtraheerde factuur-XML. HotPDF levert de vijf officiële Factur-X 1.09-regelbestanden — MINIMUM, BASIC WL, BASIC, EN 16931 en EXTENDED — onder Lib/resources/Schematron/, en HPDFSchematronFileForProfile kiest het bestand dat past bij het gedetecteerde conformiteitsniveau, zodat een EN 16931-factuur automatisch wordt gecontroleerd tegen Factur-X_1.09_EN16931.sch. De functie retourneert alleen True wanneer de container geldig is en er geen bedrijfsregel is geactiveerd

uses HPDFEInvoiceValidator;

var
  Reader: THotPDF;
  Report: THPDFEInvoiceValidationReport;
  I: Integer;
begin
  Reader := THotPDF.Create(nil);
  try
    Reader.LoadFromFile('incoming-invoice.pdf');
    if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
      Writeln('Container and business rules: OK')
    else
      Writeln('Validation found issues.');
    if Report.BusinessRulesEvaluated then
    begin
      Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
        Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
      for I := 0 to High(Report.BusinessRules) do
        if (not Report.BusinessRules[I].Skipped) and
           (Report.BusinessRules[I].Severity = stsError) then
          Writeln(string(Report.BusinessRules[I].RuleID), ': ',
            string(Report.BusinessRules[I].Message));
    end;
  finally
    Reader.Free;
  end;
end;

Elke vermelding in BusinessRules bevat de regelidentificator die is geanalyseerd uit de beweringstekst — BR-45, BR-CO-17 en de rest van het EN 16931-naamgevingsschema — plus de XPath-regelcontext, de testexpressie en een ernst (severity) die overeenkomt met het Schematron-flag-attribuut. Dit maakt het rapport bruikbaar: in plaats van een kale goedkeuring/afkeuring krijgt u de exacte zakelijke term die inconsistent is, wat uw ondersteuningsteam nodig heeft wanneer een klant vraagt waarom een factuur is geweigerd. Teams die al gestructureerde conformiteitsrapporten voor archief-PDF's genereren, kunnen deze resultaten integreren in dezelfde pijplijn die is beschreven in het artikel over het automatiseren van PDF-preflightrapporten

Waar de validator stopt, eerlijk gezegd

De Schematron-engine is gebouwd op MSXML, die XPath 1.0 evalueert. Het EN 16931-regelbestand declareert queryBinding="xslt2" en bevat 424 beweringen, waarvan de meeste alleen XPath 1.0-functies gebruiken zoals string-length en substring-after — deze draaien lokaal. Een minderheid is afhankelijk van XPath 2.0-constructies zoals typeconversies met xs:decimal of exists(); HotPDF scant elke testexpressie vooraf op bekende XPath 2.0-tokens en markeert die regels als Skipped met de ernst 'info' in plaats van een evaluatie te proberen die geruisloos zou mislukken. Het samenvattingsrecord rapporteert het aantal geëvalueerde en overgeslagen regels afzonderlijk, zodat uw logboek altijd exact vermeldt hoeveel van de regelset is uitgevoerd. Op Delphi 7, waar MSXML-koppelingen niet beschikbaar zijn, compileert de unit een stub die meldt dat de engine niet beschikbaar is zonder het container-oordeel te verstoren; de volledige engine vereist Delphi XE2 of nieuwer

De tweede grens is nog belangrijker. Een groen resultaat van HPDFValidateEInvoice betekent dat de containerstructuur voldoet en dat er geen geëvalueerde bedrijfsregel is geactiveerd — het is geen fiscaal conformiteitsattest. Nationale uitbreidingsregels, ontvangerspecifieke vereisten en de wettelijke verplichtingen rond factuurinhoud bevinden zich boven de kern van EN 16931 en vallen buiten elke bibliotheek. Beschouw de validator als de poort die structureel defecte facturen buiten uw postvak houdt en inconsistente facturen in uw postvak markeert, voer een volledige XPath 2.0-validator uit in uw releasepijplijn wanneer certificering dit vereist, en laat uw belastingadviseurs de conformiteitsvraag beantwoorden. De inbeddings-, detectie- en validatie-API's die hier worden getoond, maken deel uit van de standaard HotPDF Component voor Delphi en C++Builder, met een compleet e-factuurvoorbeeld dat alle zeven ZUGFeRD- en Factur-X-profielen dekt