Le 1er septembre 2026, la facture électronique devient un sujet opérationnel pour toutes les entreprises françaises concernées par la TVA. Je ne parle pas simplement d'envoyer un PDF par e-mail, ni de scanner une facture papier : je parle d'une facture électronique structurée, transmissible par une plateforme agréée, exploitable par les systèmes d'information, et contrôlable automatiquement.
Dans cet article, je vais expliquer ce qui change avec la réforme française, ce que signifie réellement la conformité Factur-X / EN 16931, puis comment je m'y prends pour préparer, générer et tester des factures conformes avec .NET et C#.
Le code du validateur présenté ici est disponible en open source sur GitHub : appliman/facturxvalidator.
Un site de validation de facture est également à disposition à l'adresse https://facturx.appliman.com. Il permet de déposer une facture PDF et d'obtenir un premier rapport de conformité. Pour les équipes qui préfèrent garder les fichiers en environnement interne, il est aussi possible de faire tourner le validateur en local dans un conteneur Docker à partir du dépôt open source.
Cet article est technique et pédagogique. Il ne remplace pas l'avis de votre expert-comptable, de votre conseil fiscal ou de votre plateforme agréée.
Ce qui change le 1er septembre 2026
La réforme française de la facturation électronique se déploie progressivement. La première grande échéance est le 1er septembre 2026.
À cette date :
- toutes les entreprises concernées doivent être capables de recevoir des factures électroniques ;
- les grandes entreprises et les entreprises de taille intermédiaire doivent émettre leurs factures électroniques ;
- les flux doivent passer par une plateforme agréée, qui joue le rôle d'intermédiaire technique et réglementaire ;
- certaines données de facturation, de transaction et de paiement doivent être transmises à l'administration fiscale selon les cas.
Le 1er septembre 2027, l'obligation d'émission s'étend aux PME, TPE et micro-entreprises.
La réforme distingue deux notions qu'il ne faut pas confondre :
| Notion | Ce que cela signifie |
|---|---|
| E-invoicing | Emission, transmission et réception de factures électroniques entre entreprises assujetties établies en France. |
| E-reporting | Transmission à l'administration de données de transaction ou de paiement pour des opérations qui ne passent pas toujours par une facture électronique B2B domestique. |
Le point important est le suivant : une facture électronique au sens de la réforme n'est pas un simple PDF. Elle doit respecter un format structuré et contenir ses informations obligatoires dans des champs exploitables.
timeline
title Calendrier simplifie de la reforme francaise
2026-09-01 : Reception obligatoire pour toutes les entreprises
: Emission obligatoire pour grandes entreprises et ETI
: Debut des obligations associees de transmission de donnees
2027-09-01 : Emission obligatoire pour PME, TPE et micro-entreprises
: Generalisation plus large du dispositifPourquoi cette réforme change vraiment les systèmes de facturation
Avant cette réforme, beaucoup d'entreprises considéraient qu'une facture PDF envoyée par e-mail était déjà une facture électronique. Techniquement, c'était un document numérique. Réglementairement, dans le nouveau dispositif, ce n'est pas suffisant.
Une facture électronique conforme doit être :
- structurée ;
- lisible par une machine ;
- transmise via une plateforme agréée ;
- enrichie avec les informations nécessaires à l'adressage, au contrôle fiscal et à l'automatisation comptable ;
- compatible avec les normes européennes de facture électronique.
Cela a des conséquences directes sur les logiciels de gestion :
- je dois produire des données propres, pas seulement un rendu PDF ;
- je dois gérer les identifiants d'entreprise, notamment SIREN/SIRET et TVA intracommunautaire ;
- je dois qualifier la nature de l'opération : vente de biens, prestation de services ou opération mixte ;
- je dois gérer l'adresse de livraison lorsqu'elle diffère de l'adresse de facturation ;
- je dois indiquer l'option de TVA sur les débits lorsque c'est applicable ;
- je dois être capable de transmettre ou recevoir via une plateforme agréée ;
- je dois archiver des documents probants.
Les quatre nouvelles mentions généralement mises en avant pour la réforme sont :
- le numéro SIREN du client lorsqu'il s'agit d'une entreprise ;
- la catégorie de l'opération : livraison de biens, prestation de services ou mixte ;
- l'option pour le paiement de la TVA d'après les débits, le cas échéant ;
- l'adresse complète de livraison du bien lorsqu'elle est différente de l'adresse de facturation.
Ces informations ne doivent pas seulement apparaître visuellement : elles doivent être présentes dans la donnée structurée.
EN 16931 : le modèle sémantique européen
EN 16931 est la norme européenne qui définit le modèle sémantique des éléments essentiels d'une facture électronique. J'aime la résumer ainsi : EN 16931 décrit ce qu'une facture doit vouloir dire, indépendamment du format technique utilisé pour l'écrire.
Elle organise les données de facture en termes métier :
- identification de la facture ;
- dates ;
- vendeur ;
- acheteur ;
- bénéficiaire du paiement ;
- lignes de facture ;
- quantités ;
- prix unitaires ;
- remises et charges ;
- bases de TVA ;
- taux et montants de TVA ;
- totaux ;
- devise ;
- conditions de paiement ;
- références de commande, livraison ou contrat.
Ensuite, ce modèle peut être représenté dans des syntaxes XML. Les plus fréquentes sont :
- UBL ;
- UN/CEFACT CII, utilisé par Factur-X.
flowchart LR
A["Donnees metier de facturation"] --> B["Modele semantique EN 16931"]
B --> C["Syntaxe UBL"]
B --> D["Syntaxe UN/CEFACT CII"]
D --> E["Factur-X: PDF/A-3 + XML CII embarque"]Factur-X : un PDF lisible et un XML exploitable
Factur-X est un format hybride franco-allemand. Il combine :
- un PDF/A-3 lisible par un humain ;
- un fichier XML CII embarqué dans le PDF ;
- des métadonnées qui indiquent le profil Factur-X et la présence de l'XML.
L'intérêt est très concret : le client peut ouvrir la facture comme un PDF classique, mais son logiciel comptable ou sa plateforme peut extraire l'XML et traiter automatiquement les données.
flowchart TB
P["Facture Factur-X"] --> PDF["Representation PDF/A-3 lisible"]
P --> XML["XML CII embarque"]
P --> XMP["Metadonnees PDF/XMP"]
XML --> EN["Regles EN 16931"]
XML --> BR["Regles metier et fiscales"]Factur-X définit plusieurs profils. Le bon choix dépend du niveau de détail attendu :
| Profil | Usage typique |
|---|---|
| MINIMUM | Données minimales d'identification. |
| BASIC WL | Données de base sans lignes détaillées complètes. |
| BASIC | Facture plus détaillée, avec premières informations de lignes. |
| EN 16931 | Profil de référence pour couvrir le coeur de la norme européenne. |
| EXTENDED | Données enrichies pour des cas complexes : logistique, douane, remises avancées, supply chain. |
Pour un flux B2B sérieux, je vise généralement le profil EN 16931, car il force à traiter les vrais sujets de conformité : lignes, TVA, totaux, parties, règles conditionnelles.
Ce que je teste pour parler de conformité
Tester une facture Factur-X ne consiste pas à regarder si le fichier porte l'extension .pdf. Je découpe toujours les contrôles en couches.
flowchart TD
A["Fichier depose"] --> B{"Signature PDF ?"}
B -->|Non| X["Rejet: ce n'est pas un PDF"]
B -->|Oui| C{"PDF/A-3 plausible ?"}
C --> D{"XML embarque present ?"}
D -->|Non| Y["Erreur: pas de Factur-X exploitable"]
D -->|Oui| E{"XML bien forme ?"}
E -->|Non| Z["Erreur XML"]
E -->|Oui| F["Validation XSD CII"]
F --> G["Validation Schematron EN 16931 / Factur-X"]
G --> H["Controles metier: TVA, totaux, champs obligatoires"]
H --> I["Rapport de conformite"]Dans mes contrôles, je distingue :
- les erreurs bloquantes ;
- les avertissements ;
- les informations utiles au diagnostic.
Une facture est non conforme si :
- elle n'est pas un PDF valide ;
- elle ne contient pas d'XML embarqué compatible ;
- l'XML n'est pas bien formé ;
- le profil déclaré ne correspond pas au contenu ;
- des champs obligatoires sont absents ;
- les règles EN 16931 échouent ;
- les montants sont incohérents ;
- les montants de TVA ne correspondent pas aux bases, taux et totaux ;
- le PDF et l'XML ne racontent pas la même facture.
Préparer son modèle de données en C#
Avant de générer du XML, je commence par créer un modèle métier propre. Le plus gros risque dans un projet Factur-X n'est pas le XML lui-même : c'est de découvrir trop tard que les données métier ne sont pas disponibles ou pas fiables.
Voici un modèle volontairement simplifié :
public sealed record Invoice(
string Number,
DateOnly IssueDate,
Party Seller,
Party Buyer,
string Currency,
IReadOnlyList<InvoiceLine> Lines,
TaxSummary TaxSummary,
PaymentTerms PaymentTerms);
public sealed record Party(
string Name,
string Siren,
string? VatNumber,
PostalAddress Address);
public sealed record PostalAddress(
string Street,
string PostalCode,
string City,
string CountryCode);
public sealed record InvoiceLine(
string Description,
decimal Quantity,
string UnitCode,
decimal UnitPrice,
decimal VatRate);
public sealed record TaxSummary(
decimal TaxBasisTotal,
decimal TaxTotal,
decimal GrandTotal);
public sealed record PaymentTerms(
DateOnly DueDate,
string PaymentMeansCode,
string? Iban);
Je valide ce modèle avant même de penser XML :
public static IReadOnlyList<string> ValidateBusinessInvoice(Invoice invoice)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(invoice.Number))
{
errors.Add("Le numero de facture est obligatoire.");
}
if (string.IsNullOrWhiteSpace(invoice.Seller.Siren))
{
errors.Add("Le SIREN du vendeur est obligatoire.");
}
if (string.IsNullOrWhiteSpace(invoice.Buyer.Siren))
{
errors.Add("Le SIREN du client est obligatoire pour une facture B2B francaise.");
}
if (invoice.Lines.Count == 0)
{
errors.Add("Une facture EN 16931 doit contenir au moins une ligne.");
}
var expectedTotal = invoice.TaxSummary.TaxBasisTotal + invoice.TaxSummary.TaxTotal;
if (Math.Abs(expectedTotal - invoice.TaxSummary.GrandTotal) > 0.01m)
{
errors.Add("Le total TTC ne correspond pas au total HT + TVA.");
}
return errors;
}
Cette première validation est volontairement métier. Elle ne remplace pas EN 16931, mais elle évite de générer des fichiers manifestement faux.
Générer un XML CII avec XDocument
Pour construire l'XML CII, j'utilise une API XML structurée, pas de concaténation de chaînes. XDocument est suffisant pour illustrer l'approche.
using System.Xml.Linq;
public static XDocument BuildCiiInvoice(Invoice invoice)
{
XNamespace rsm = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100";
XNamespace ram = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100";
XNamespace udt = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100";
return new XDocument(
new XElement(rsm + "CrossIndustryInvoice",
new XElement(rsm + "ExchangedDocumentContext",
new XElement(ram + "GuidelineSpecifiedDocumentContextParameter",
new XElement(ram + "ID", "urn:cen.eu:en16931:2017"))),
new XElement(rsm + "ExchangedDocument",
new XElement(ram + "ID", invoice.Number),
new XElement(ram + "TypeCode", "380"),
new XElement(ram + "IssueDateTime",
new XElement(udt + "DateTimeString",
new XAttribute("format", "102"),
invoice.IssueDate.ToString("yyyyMMdd")))),
new XElement(rsm + "SupplyChainTradeTransaction",
invoice.Lines.Select((line, index) => BuildLine(ram, line, index + 1)),
BuildHeaderSettlement(ram, invoice))));
}
private static XElement BuildLine(XNamespace ram, InvoiceLine line, int index)
{
return new XElement(ram + "IncludedSupplyChainTradeLineItem",
new XElement(ram + "AssociatedDocumentLineDocument",
new XElement(ram + "LineID", index.ToString())),
new XElement(ram + "SpecifiedTradeProduct",
new XElement(ram + "Name", line.Description)),
new XElement(ram + "SpecifiedLineTradeAgreement",
new XElement(ram + "NetPriceProductTradePrice",
new XElement(ram + "ChargeAmount", line.UnitPrice.ToString("0.00")))),
new XElement(ram + "SpecifiedLineTradeDelivery",
new XElement(ram + "BilledQuantity",
new XAttribute("unitCode", line.UnitCode),
line.Quantity.ToString("0.####"))));
}
Le code ci-dessus est incomplet pour une facture de production, mais il montre le principe : je mappe mon modèle métier vers des éléments CII, et j'évite d'écrire du XML comme du texte.
Les contrôles métier que je fais avant validation normative
Avant d'appeler des validateurs XSD ou Schematron, je vérifie les invariants qui viennent de mon domaine :
| Contrôle | Exemple |
|---|---|
| Identité vendeur | SIREN, nom, adresse, TVA si applicable. |
| Identité acheteur | SIREN client, adresse, pays. |
| Numérotation | Numéro unique et non vide. |
| Dates | Date d'émission, date d'échéance cohérente. |
| Lignes | Quantité positive, prix cohérent, unité connue. |
| TVA | Taux autorisé, base et montant exacts. |
| Totaux | HT + TVA = TTC, tolérance d'arrondi maîtrisée. |
| Devise | Code ISO, cohérence entre lignes et totaux. |
| Mentions réforme | catégorie de l'opération, livraison, TVA sur les débits si applicable. |
flowchart LR
ERP["ERP / logiciel de gestion"] --> M["Modele Invoice C#"]
M --> V1["Validation metier"]
V1 --> XML["Generation XML CII"]
XML --> V2["Validation normative"]
V2 --> PDF["Assemblage PDF/A-3 Factur-X"]
PDF --> V3["Validation finale du fichier"]Valider l'XML : XSD puis Schematron
Je distingue deux familles de validation :
- XSD : est-ce que la structure XML respecte la grammaire attendue ?
- Schematron : est-ce que les règles métier de la norme sont satisfaites ?
Le XSD répond à des questions de forme :
- cet élément existe-t-il au bon endroit ?
- ce type de donnée est-il valide ?
- cette cardinalité est-elle respectée ?
Schematron répond à des questions métier :
- si tel code TVA est utilisé, les montants attendus sont-ils présents ?
- si une exonération est déclarée, la raison est-elle indiquée ?
- les totaux documentaires correspondent-ils aux lignes ?
- le profil déclaré est-il cohérent ?
Un squelette de validation XML en C# peut ressembler à ceci :
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
public sealed class XmlValidationIssue
{
public required string Severity { get; init; }
public required string Message { get; init; }
}
public static IReadOnlyList<XmlValidationIssue> ValidateWithXsd(
XDocument document,
XmlSchemaSet schemas)
{
var issues = new List<XmlValidationIssue>();
document.Validate(schemas, (sender, args) =>
{
issues.Add(new XmlValidationIssue
{
Severity = args.Severity.ToString(),
Message = args.Message
});
});
return issues;
}
Pour Schematron, j'utilise généralement une étape dédiée. Selon l'outillage choisi, elle peut passer par :
- un validateur Java intégré au pipeline CI ;
- une transformation XSLT générée depuis les règles Schematron ;
- un service interne ;
- un conteneur de validation ;
- une bibliothèque spécialisée.
Le point clé est d'intégrer cette validation dans la chaîne de build et dans les tests automatisés, pas seulement dans une vérification manuelle de fin de projet.
Extraire et tester une facture Factur-X reçue
Pour contrôler une facture reçue, je pars du PDF. Mon pipeline est :
- lire le fichier ;
- vérifier la signature PDF ;
- ouvrir le PDF avec un lecteur robuste ;
- détecter les pièces jointes embarquées ;
- trouver
factur-x.xml,zugferd-invoice.xmlou un XML CII compatible ; - parser l'XML de manière sécurisée ;
- valider XSD/Schematron ;
- extraire les métadonnées utiles ;
- produire un rapport clair.
Exemple de chargement XML sécurisé :
using System.Xml;
using System.Xml.Linq;
public static XDocument LoadXmlSecurely(string xml)
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersFromEntities = 0,
MaxCharactersInDocument = 10_000_000
};
using var reader = new StringReader(xml);
using var xmlReader = XmlReader.Create(reader, settings);
return XDocument.Load(xmlReader, LoadOptions.None);
}
Je bloque volontairement les DTD et les résolveurs externes. Une facture est un document métier, pas une raison d'autoriser un parseur XML à résoudre des ressources externes.
Produire un rapport exploitable
Un validateur qui répond seulement "valide" ou "invalide" n'aide pas beaucoup. Je préfère produire un rapport structuré.
public sealed class ValidationReport
{
public required string FileName { get; init; }
public DateTimeOffset UploadedAt { get; init; }
public bool IsValid => Errors.Count == 0 && Warnings.Count == 0;
public List<ValidationIssue> Errors { get; } = [];
public List<ValidationIssue> Warnings { get; } = [];
public List<ValidationIssue> Information { get; } = [];
public ExtractedInvoiceMetadata Metadata { get; } = new();
}
public sealed class ValidationIssue
{
public required string Severity { get; init; }
public required string Message { get; init; }
}
Dans l'interface, je veux voir :
- le nom du fichier ;
- le statut global ;
- le profil détecté ;
- le nom du XML embarqué ;
- le numéro de facture ;
- la date ;
- le vendeur ;
- l'acheteur ;
- les montants HT, TVA et TTC ;
- la devise ;
- les erreurs ;
- les avertissements ;
- les informations de diagnostic.
Tester avec MSTest et bUnit
Pour éviter les régressions, je teste deux niveaux.
D'abord, je teste les règles simples du modèle :
[TestClass]
public sealed class ValidationReportTests
{
[TestMethod]
public void IsValid_ReturnsFalse_WhenWarningsArePresent()
{
var report = new ValidationReport
{
FileName = "invoice.pdf",
UploadedAt = DateTimeOffset.UtcNow
};
report.Warnings.Add(new ValidationIssue
{
Severity = "Warning",
Message = "Missing optional metadata."
});
Assert.IsFalse(report.IsValid);
}
}
Ensuite, je teste l'intégration du composant d'upload avec bUnit :
[TestMethod]
public void UploadAndAnalyze_DisplaysValidAndInvalidInvoiceReports()
{
using var context = new BunitContext();
var fileStorage = new CapturingFileStorageService();
var validationService = new FakeInvoiceValidationService();
context.Services.AddSingleton<IFileStorageService>(fileStorage);
context.Services.AddSingleton<IFacturXValidationService>(validationService);
var component = context.Render<UploadInvoiceComponent>();
var validInvoice = CreateInvoiceUpload("facture-conforme.pdf");
var invalidInvoice = CreateInvoiceUpload("facture-non-conforme.pdf");
component.FindComponent<InputFile>().UploadFiles(validInvoice, invalidInvoice);
Assert.IsFalse(component.Find("button.btn-primary").HasAttribute("disabled"));
component.Find("button.btn-primary").Click();
component.WaitForAssertion(() =>
{
Assert.Contains("facture-conforme.pdf", component.Markup);
Assert.Contains("facture-non-conforme.pdf", component.Markup);
Assert.Contains("Conforme", component.Markup);
Assert.Contains("Non conforme", component.Markup);
});
}
Ce test est important parce qu'il valide le parcours utilisateur :
- sélection de fichiers ;
- activation du bouton d'analyse ;
- stockage temporaire ;
- validation ;
- affichage d'un rapport conforme et d'un rapport non conforme.
Mettre la conformité dans une CI
Je ne veux pas découvrir une régression Factur-X en production. Je mets donc la validation dans la CI.
flowchart TD
A["Pull request"] --> B["dotnet restore"]
B --> C["dotnet build -c Release"]
C --> D["dotnet test -c Release"]
D --> E["Tests unitaires metier"]
D --> F["Tests bUnit upload"]
D --> G["Fixtures Factur-X conformes et non conformes"]
G --> H{"Tous les rapports attendus ?"}
H -->|Oui| I["PR validable"]
H -->|Non| J["Correction obligatoire"]J'ajoute au minimum :
- une facture conforme ;
- une facture non conforme sans XML ;
- une facture avec XML mal formé ;
- une facture avec mauvais profil ;
- une facture avec incohérence de TVA ;
- une facture avec champs obligatoires absents ;
- une facture avec montants arrondis à la limite.
L'objectif n'est pas seulement de couvrir du code. L'objectif est de couvrir des risques métier.
Points d'attention en production
Pour passer en production, je surveille plusieurs sujets.
PDF/A-3 réel
Un PDF qui commence par %PDF- n'est pas automatiquement un PDF/A-3. La conformité PDF/A-3 implique des métadonnées, des profils colorimétriques, des contraintes d'archivage et une déclaration correcte des fichiers embarqués.
Je recommande d'utiliser un outil spécialisé pour produire et valider le PDF/A-3.
Cohérence PDF et XML
Le PDF visible et l'XML doivent représenter la même facture. Si le PDF indique un total TTC de 120 euros et que l'XML indique 118 euros, le fichier est dangereux, même si l'XML est bien formé.
Profil déclaré
Le GuidelineID doit correspondre au profil réellement produit. Déclarer EN 16931 avec un XML de niveau BASIC est une erreur classique.
TVA et arrondis
Les écarts d'arrondi doivent être maîtrisés. Je fixe une tolérance faible, documentée, et je teste les cas limites.
Plateforme agréée
La conformité du fichier ne suffit pas. Dans le cadre français, les factures doivent transiter par une plateforme agréée. Je dois donc tester :
- l'adressage du client ;
- les statuts de transmission ;
- les rejets ;
- les corrections ;
- les avoirs ;
- les cas e-reporting.
Ressources de référence
Pour suivre la réforme et éviter de baser mon implémentation sur des hypothèses, je m'appuie sur les sources suivantes :
- impots.gouv.fr : Je passe à la facturation électronique
- impots.gouv.fr : À partir de quand suis-je concerné par la réforme ?
- economie.gouv.fr : Tout savoir sur la facturation électronique pour les entreprises
- economie.gouv.fr : Mentions obligatoires d'une facture
- Commission européenne : Documentation sur le standard eInvoicing EN 16931
- FNFE-MPE : Factur-X
- GitHub : appliman/facturxvalidator
Conclusion : pourquoi j'ai mis en place ce site de vérification
J'ai mis en place ce site pour répondre à un besoin très concret : pouvoir déposer une facture PDF, vérifier si elle ressemble réellement à une facture Factur-X exploitable, extraire ses métadonnées, détecter les erreurs évidentes, et produire un rapport compréhensible.
Le site est disponible publiquement à l'adresse https://facturx.appliman.com. Pour les usages internes, les tests automatisés ou les environnements où les factures ne doivent pas sortir du réseau de l'entreprise, le projet peut aussi être exécuté localement dans un conteneur Docker.
J'ai aussi choisi de publier le projet en open source dans le dépôt appliman/facturxvalidator, afin que chacun puisse lire le code, l'exécuter localement, l'adapter à ses propres contrôles et contribuer à améliorer la validation.
Ce site ne remplace pas une plateforme agréée, ni une validation réglementaire complète, ni les contrôles d'un expert-comptable. En revanche, il apporte une première barrière utile avant l'envoi :
- la facture est-elle bien un PDF ?
- contient-elle un XML Factur-X ?
- l'XML est-il lisible ?
- le profil est-il détectable ?
- les champs essentiels sont-ils présents ?
- les montants sont-ils cohérents ?
- le résultat est-il conforme, en avertissement ou non conforme ?
Je vois cet outil comme un filet de sécurité pour les équipes qui préparent la transition 2026-2027. Plus tôt on teste les fichiers réels, plus tôt on découvre les problèmes de mapping, de TVA, de profil, d'arrondi ou de données manquantes.
La conformité Factur-X / EN 16931 n'est pas un bouton magique à activer dans un logiciel. C'est une chaîne complète : données propres, génération correcte, validation stricte, transmission via plateforme agréée et suivi des statuts. C'est précisément pour rendre cette chaîne visible que ce site existe.
Aucun commentaire publié pour le moment.
Ajouter un commentaire