Article Technique

ZUGFeRD and Factur-X E-Invoices in Delphi with HotPDF

HotPDF génère, détecte et valide des factures électroniques ZUGFeRD et Factur-X depuis Delphi et C++Builder. AddFacturXAssociatedFile intègre le fichier XML de facture EN 16931 dans un conteneur PDF/A-3, DetectFacturXInvoice reconnaît une facture hybride entrante, et HPDFValidateEInvoice applique les règles Schematron officielles EN 16931 au fichier XML embarqué. Cet article détaille ces trois étapes et présente en toute transparence les limites du moteur de validation

La contrainte derrière cet ensemble de fonctionnalités est réglementaire, et non technique. L'Allemagne et la France mettent en place la facturation électronique structurée obligatoire pour les transactions nationales B2B, et ces deux mandats convergent vers le même modèle hybride : la facture PDF produite par votre ERP doit contenir son pendant XML structuré. Une application Delphi de comptabilité ou d'ERP qui émet aujourd'hui de simples factures PDF a l'obligation de commencer à émettre des factures Factur-X ou ZUGFeRD. La différence entre ces deux appellations est minime : Factur-X et ZUGFeRD désignent la même norme franco-allemande publiée sous deux étiquettes

Qu'est-ce qui fait d'un PDF une facture Factur-X valide ?

Une facture Factur-X valide est un fichier PDF/A-3 (ISO 19005-3) qui intègre exactement un fichier XML de facture structurée comme fichier associé, lié depuis le catalogue du document et déclaré dans les métadonnées d'extension XMP. Trois couches doivent concorder. Premièrement, le conteneur doit être conforme à la norme PDF/A-3, le profil d'archivage qui — contrairement aux profils PDF/A-1 et PDF/A-2 — autorise les pièces jointes de tout type. Deuxièmement, le XML de facture, nommé factur-x.xml pour les profils standard ou xrechnung.xml pour la variante allemande XRechnung, doit être enregistré dans le tableau /AF du catalogue et dans l'arborescence /Names /EmbeddedFiles, avec une entrée AFRelationship indiquant la nature de la pièce jointe : Alternative signifie que le XML est une représentation équivalente de la facture visible, sémantique requise par la plupart des profils. Troisièmement, le schéma d'extension XMP de Factur-X doit indiquer le nom du fichier intégré, son niveau de conformité et le type de document, de sorte qu'un système récepteur puisse classifier la facture sans même analyser le fichier XML

Le contenu XML lui-même suit le modèle sémantique de la norme EN 16931, la norme européenne définissant la facture de base : acheteur, vendeur, lignes de facture, ventilation de la TVA, montant à payer, chacun identifié par un numéro de terme commercial (BT-1 pour le numéro de facture, BT-5 pour la devise, etc.). La validation EN 16931 s'effectue donc sur deux couches distinctes. La validation de schéma XML vérifie que le document est un fichier CII bien formé avec les bons noms d'éléments. Les règles Schematron vérifient quant à elles la cohérence du contenu — par exemple, que la règle BR-CO-15 est respectée et que le montant total de la facture correspond réellement à la somme des montants nets des lignes augmentée de la TVA, ou encore que les termes obligatoires sont présents pour le profil déclaré. Un fichier peut passer la validation de schéma et demeurer une facture non valide, c'est pourquoi la couche de règles métier existe

Pourquoi ZUGFeRD 2.5 n'a pas besoin d'un nouveau conteneur

ZUGFeRD 2.5 ne change rien au niveau du conteneur PDF : les URN de recommandation, le schéma XMP et le jeton de version sont identiques à ceux de ZUGFeRD 2.3 / Factur-X 1.0. Cela surprend la plupart des développeurs, il convient donc de citer la source. La spécification Factur-X 1.09 indique que le numéro de version dans l'URI du schéma d'extension PDF/A n'est pas lié au numéro de version de la spécification XML, et que la version des instances de facture Factur-X reste 1.0 — à la fois dans le champ XMP fx:Version et dans le champ BT-24 de factur-x.xml. Le corpus d'exemples officiel de ZUGFeRD 2.5 le confirme : chaque échantillon intègre factur-x.xml sous un URN urn:factur-x.eu:1p0 avec une valeur fx:Version à 1.0. Ce que la version 2.5 apporte réellement réside à l'intérieur du modèle de données XML — de nouveaux éléments comme les sous-lignes de facturation et les identifiants de lots — qui relève de la couche métier générée par votre application, et non de la couche conteneur gérée par la bibliothèque PDF

HotPDF intègre directement cette logique. AddFacturXAssociatedFile accepte un argument Version et le normalise via HPDFFacturXNormalizeVersion avant d'écrire le XMP. Ainsi, l'appelant transmettant l'alias 2p5 émet toujours le jeton conforme à la spécification au lieu d'inventer une version de schéma XMP 2p5 qu'aucun validateur n'accepterait. Les utilitaires de détection traitent la version 2p5 comme équivalente lors de la lecture, préservant la symétrie. Si une bibliothèque ou un outil prétend écrire un conteneur spécifique ZUGFeRD 2.5, sachez que la spécification elle-même affirme qu'il n'en existe pas

Intégration du fichier XML de facture depuis Delphi

THotPDF.AddFacturXAssociatedFile transforme une opération classique de génération de PDF/A-3 en facture Factur-X en un seul appel : il intègre les octets XML en tant que fichier associé, enregistre factur-x.xml dans le tableau /AF du catalogue et dans l'arborescence /Names /EmbeddedFiles, et émet les métadonnées d'extension XMP correspondantes. Vous dessinez la page de facture lisible par l'homme avec l'API classique de HotPDF ; le travail sur le conteneur se résume à une ligne entre BeginDoc et 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;

Les paramètres par défaut suivent la spécification : nom de fichier factur-x.xml, relation Alternative, version 1.0. AddUBLSupplementaryFile implémente la section 6.4 de la spécification Factur-X 1.09, qui autorise l'intégration d'une représentation UBL de la même facture en tant que seconde pièce jointe nommée factur-xubl.xml avec le type MIME application/xml et la relation Alternative. L'ordre importe ici, et HotPDF applique la contrainte imposée par la spécification : le fichier UBL constitue une vue supplémentaire, et non la facture principale, il doit donc être ajouté après l'enregistrement du CII principal via AddFacturXAssociatedFile. Notez que la page dessinée et le fichier XML intégré doivent indiquer la même facture — un PDF affichant un total et en encodant un autre va précisément à l'encontre de l'objectif de la facturation hybride. L'aspect PDF/A-3 de ce flux, y compris l'intention de sortie (OutputIntent) et l'intégration des polices, est traité plus en détail dans l'article complémentaire sur la validation PDF/A, PDF/X et PDF/UA dans Delphi

Comment détecter une facture Factur-X dans un PDF entrant ?

THotPDF.DetectFacturXInvoice répond aux besoins de réception : chargez n'importe quel PDF et déterminez en un seul appel s'il s'agit d'une facture hybride, quel profil elle déclare et quelles pièces jointes elle transporte. La fonction renvoie un enregistrement THPDFFacturXInvoiceInfo contenant le nom du fichier XML intégré, le nom et le type de fichier déclarés dans le XMP, le jeton de version, le niveau de conformité, l'URN de recommandation et la valeur AFRelationship. Deux autres champs signalent les représentations supplémentaires : UBLFileName est renseigné en présence d'une pièce jointe factur-xubl.xml, et EDIFACTFileName en présence d'une vue EDIFACT — aucun des deux n'affecte le verdict de détection, car une représentation supplémentaire ne peut exister sans la facture principale CII

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;

La détection lit les mêmes signaux qu'un validateur conforme — le schéma d'extension XMP et le registre des fichiers associés — plutôt que de s'appuyer uniquement sur le nom des fichiers. L'objet chargé expose également les métadonnées XMP et le dictionnaire d'informations pour inspection ou correction, un flux décrit dans l'article sur la modification des métadonnées dans les documents PDF chargés

Règles métier ou schéma : que signifie la validation EN 16931 ?

HPDFValidateEInvoice, une fonction autonome de l'unité HPDFEInvoiceValidator, exécute les deux couches de validation successivement : d'abord la validation de la structure du conteneur via ValidateFacturXInvoice, puis les règles métier Schematron EN 16931 sur le fichier XML extrait. HotPDF fournit les temps de règles officiels de Factur-X 1.09 — MINIMUM, BASIC WL, BASIC, EN 16931 et EXTENDED — sous le répertoire Lib/resources/Schematron/. La fonction HPDFSchematronFileForProfile sélectionne le fichier correspondant au niveau de conformité détecté, de sorte qu'une facture EN 16931 soit automatiquement vérifiée par rapport à Factur-X_1.09_EN16931.sch. La fonction renvoie True uniquement si le conteneur est valide et qu'aucune règle métier n'a échoué

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;

Chaque entrée de BusinessRules porte l'identifiant de règle analysé à partir de l'assertion — BR-45, BR-CO-17, etc. — ainsi que l'expression de test et un niveau de gravité. Cela rend le rapport exploitable : au lieu d'un simple succès/échec, vous obtenez l'anomalie exacte, ce dont votre équipe de support a besoin lorsqu'un client demande pourquoi une facture a été rejetée. Les équipes générant déjà des rapports de conformité rapport de contrôle en amont PDF peuvent intégrer ces résultats dans le pipeline décrit dans l'article sur l'automatisation des rapports de contrôle en amont PDF

Où s'arrête le validateur, en toute honnêteté

Le moteur Schematron est basé sur MSXML, qui évalue XPath 1.0. Le fichier de règles EN 16931 déclare queryBinding="xslt2" et contient 424 assertions, dont la plupart utilisent uniquement des fonctions XPath 1.0 comme string-length et substring-after — qui s'exécutent nativement. Une minorité repose sur des structures XPath 2.0 comme les conversions xs:decimal ou exists(). HotPDF analyse chaque expression de test à la recherche de jetons XPath 2.0 connus et marque ces règles comme Skipped (ignorées) avec un niveau d'information, plutôt que de tenter une évaluation qui échouerait silencieusement. Le rapport récapitule séparément les règles évaluées et ignorées, de sorte que votre journal indique exactement ce qui a été testé. Sous Delphi 7, où les liaisons MSXML ne sont pas disponibles, l'unité compile un bouchon signalant le moteur comme indisponible sans altérer le verdict du conteneur ; le moteur complet requiert Delphi XE2 ou supérieur

La seconde limite importe encore plus. Un résultat positif de HPDFValidateEInvoice signifie que la structure du conteneur est conforme et qu'aucune règle métier évaluée n'a échoué — il ne s'agit pas d'une validation de conformité fiscale. Les règles d'extension nationales, les exigences spécifiques des récepteurs et les obligations légales sur le contenu des factures se situent au-dessus de la norme EN 16931 et en dehors de toute bibliothèque. Utilisez le validateur comme filtre pour écarter les factures structurellement incorrectes et signaler les incohérences, utilisez un validateur XPath 2.0 complet dans votre processus de déploiement si la certification l'exige, et laissez vos conseillers fiscaux gérer la conformité. Les API d'intégration, de détection et de validation présentées ici font partie de la version standard de HotPDF Component pour Delphi et C++Builder, avec un exemple complet de facturation électronique couvrant les sept profils ZUGFeRD et Factur-X