Factur-X 2026 : Guide d'implémentation pour les PME du BTP
Contexte réglementaire : pourquoi Factur-X devient obligatoire
Depuis janvier 2024, la France impose progressivement la facturation électronique via Factur-X (norme e-INVOICING UBL 2.1 avec PDF/A-3 embarqué). À partir de juin 2026, TOUS les échanges B2B interentreprises doivent transiter par la plateforme Chorus Pro ou un opérateur qualifié, en format structuré Factur-X.
Pour les PME du BTP, cela change la donne :
- Plus de PDF "classiques" : le fichier facture devient une structure XML sérialisée dans un PDF/A-3
- Double lecture : lecteur humain (PDF) + machine (XML embarqué) pour une interopérabilité maximale
- Conformité RGPD/douanes : traçabilité intégrale, audit natif
Concrètement ? Si vous invoicez les clients B2B en 2026 sans Factur-X, vous risquez une amende de €750 à €15 000 par facture non-conforme. Pas de grâce.
Pourquoi ce deadline terrorise les devs
Les développeurs SaaS construction l'admettent rarement, mais Factur-X paraît horrifiant à première vue :
- XML Schema complexe (2 MB de spec Afnor)
- PDF/A-3 embedding (nécessite une lib spéciale, pas juste iText/PDFBox)
- Signature numérique optionnelle mais recommandée (certificats, PKI)
- Intégration Chorus Pro (API asynchrone, files d'attente, retry logic)
Résultat : beaucoup de solutions SaaS BTP lancent des "stopgap" bricolés — un PDF + un XML flatté dans un email — et se promettent "on migrera plus tard". Spoiler : plus tard = juin 2026, à quelques semaines du deadline, quand les clients appellent le support en panique.
L'architecture Factur-X minimale (expliquée simplement)
Une facture Factur-X, c'est trois couches :
1. Le fichier racine : PDF/A-3
Un PDF classique, lisible humain, conforme PDF/A-3 (archivable sur 20 ans). Vous pouvez le générer avec :
- iText 7+ (Java, .NET)
- PyPDF2 / reportlab (Python)
- pdfkit + wkhtmltopdf (Node.js)
L'important : utiliser le profil PDF/A-3b (pas A-3a), qui autorise les pièces jointes non-validées.
2. L'XML embarqué (UBL invoice)
Structure XML au format UN/CEFACT (United Nations). À minima, ces champs :
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<!-- Identifiant unique -->
<cbc:ID>INV-2026-001234</cbc:ID>
<cbc:IssueDate>2026-01-15</cbc:IssueDate>
<!-- Devise, montants -->
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">10000.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">10000.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">12000.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="EUR">12000.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<!-- Vendeur -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:Name>ACME BTP SARL</cbc:Name>
<cac:PartyIdentification>
<cbc:ID schemeID="FR:SIRET">12345678901234</cbc:ID>
</cac:PartyIdentification>
<cac:PartyLegalEntity>
<cbc:RegistrationName>ACME BTP SARL</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>
<!-- Acheteur -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:Name>Client XYZ</cbc:Name>
<cac:PartyIdentification>
<cbc:ID schemeID="FR:SIRET">98765432109876</cbc:ID>
</cac:PartyIdentification>
</cac:Party>
</cac:AccountingCustomerParty>
<!-- Lignes d'article -->
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity>5</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">2500.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Pose carrelage 10m² salle de bain</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID> <!-- Standard rate 20% -->
<cbc:Percent>20</cbc:Percent>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">500.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
<!-- Sommes de TVA -->
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">2000.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">10000.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">2000.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>20</cbc:Percent>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
</Invoice>
Piège classique : omettre <cbc:ID schemeID="FR:SIRET"> pour le vendeur. La plateforme Chorus Pro rejette silencieusement la facture si le SIRET n'est pas présent.
3. L'assembly : embarquer l'XML dans le PDF
Avec Apache PDFBox (Java) :
PDDocument doc = PDDocument.load(pdfFile);
PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc);
PDEmbeddedFilesNameTreeNode effTree = new PDEmbeddedFilesNameTreeNode();
// Créer une pièce jointe XML
PDComplexFileSpecification spec = new PDComplexFileSpecification();
spec.setFile("facture.xml");
spec.setEmbeddedFile(new PDEmbeddedFile(doc, xmlBytes));
effTree.setNames(Collections.singletonMap("facture.xml", spec));
names.setEmbeddedFiles(effTree);
doc.getDocumentCatalog().setNames(names);
doc.save(outputFile);
Résultat : un PDF téléchargeable en un clic qui contient l'XML complet — lisible par un humain via Acrobat Reader, parsable par Chorus Pro.
Les 5 erreurs les plus coûteuses
-
Oublier la TVA intra-communautaire (reverse charge)
- Si acheteur UK/DE : à partir de 2026, TVA à 0% + déclaration spéciale
- Code de TVA incorrect = rejet au upload Chorus
-
Encoder en UTF-8 sans BOM mais avec accents mal mappés
- XML doit être
<?xml version="1.0" encoding="UTF-8"?> - Les caractères accentués (é, è, ç) crashent certains parseurs Chorus
- Solution : valider avec xmllint ou un parser en streaming
- XML doit être
-
Oublier la structure de signature (même non signée)
- Factur-X optionnelle mais recommandée : inclure une balise
<cac:Signature>vide - Sinon, certains lecteurs rejettent comme "incomplete"
- Factur-X optionnelle mais recommandée : inclure une balise
-
PDF/A-3 au lieu de PDF/A-3b
- PDF/A-3a = strict (tous les fonts embarqués). Trop dur pour le BTP
- PDF/A-3b = permissive. Utilise toujours b
-
Chorus Pro timeout = crash silencieux
- L'API Chorus ne retourne pas d'erreur avant 60 secondes
- Implémenter un retry exponentiel avec timeout de 10s + queue locale
Implémentation step-by-step en Node.js
Voici une fonction minimale pour générer Factur-X avec Anodos ou n'importe quel SaaS construction :
// npm install pdfkit pdfkit-flatten xml2js axios
const PDFDocument = require('pdfkit');
const xml2js = require('xml2js');
const fs = require('fs');
const axios = require('axios');
async function generateFacturX(invoice) {
// 1. Générer l'XML UBL
const xmlBuilder = new xml2js.Builder({
xmldec: { version: '1.0', encoding: 'UTF-8' }
});
const invoiceObj = {
Invoice: {
$: {
xmlns: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
'xmlns:cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
'xmlns:cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
},
'cbc:ID': [invoice.number],
'cbc:IssueDate': [new Date().toISOString().split('T')[0]],
'cbc:DocumentCurrencyCode': ['EUR'],
// ... populate remaining fields
}
};
const xmlString = xmlBuilder.buildObject(invoiceObj);
// 2. Créer PDF avec pdfkit
const pdfDoc = new PDFDocument({ bufferPages: true });
pdfDoc.fontSize(12).text(`Facture ${invoice.number}`, 50, 50);
pdfDoc.text(`Montant HT : ${invoice.amount_ht} EUR`, 50, 100);
pdfDoc.text(`TVA (20%) : ${invoice.amount_ht * 0.2} EUR`, 50, 120);
pdfDoc.text(`Montant TTC : ${invoice.amount_ttc} EUR`, 50, 140);
// 3. Embarquer l'XML (nécessite pdfkit-flatten ou lib native)
// Note : pdfkit ne supporte PAS nativement PDF/A-3
// Utiliser iText (Java) ou PyPDF2 (Python) pour cette étape
return pdfDoc;
}
// Upload vers Chorus Pro
async function uploadToChorusPro(pdfBytes, chorusToken) {
const formData = new FormData();
formData.append('file', new Blob([pdfBytes]), 'facture.pdf');
formData.append('supplierSiret', '12345678901234');
try {
const response = await axios.post(
'https://api.chorus-pro.gouv.fr/invoices',
formData,
{
headers: {
'Authorization': `Bearer ${chorusToken}`,
'Content-Type': 'multipart/form-data'
},
timeout: 10000
}
);
console.log('Uploaded:', response.data.invoiceId);
} catch (err) {
console.error('Upload failed:', err.message);
// Retry logic...
}
}
Note pro : pdfkit est excellent pour générer du HTML→PDF, mais pour PDF/A-3, vous aurez besoin d'une lib plus spécialisée. En production, utiliser iText 7 (Java), ReportLab (Python) ou pdfLibreOffice (API).
Timeline réaliste pour migration Factur-X
| Phase | Timing | Action |
|---|---|---|
| Audit | Jan–Feb 2026 | Vérifier la stack actuelle (API facturation, format export, intégration comptable) |
| Dev + QA | Feb–Apr 2026 | Implémenter génération XML, PDF/A-3, tests unitaires sur 50+ cas d'usage |
| Staging Chorus | Apr–May 2026 | Créer compte sandbox Chorus Pro, tester uploads, vérifier rejets |
| Rollout progressif | May–Jun 2026 | Activer pour 10% clients, monit. erreurs, ramp-up à 100% |
| Support intensif | Jun–Sep 2026 | Hotline clients, retry logic auto, monitoring Chorus API status |
Ressources officielles
- Spec Factur-X : https://www.factur-x.gouv.fr/ (Afnor, 300 pages)
- Chorus Pro Docs : https://communaute.chorus-pro.gouv.fr/
- Validateur en ligne : https://www.factur-x.info/validator
-
Stack overflow :
[factur-x]+[e-invoicing]
Conclusion : Factur-X n'est pas du chaos, c'est une spec
Le saut mental à faire : Factur-X n'est pas une "complexité française" gratuite. C'est une implémentation de UN/CEFACT invoice 2.0, utilisée par 27 pays européens. Une fois que vous avez cracké le XML et l'embarquement PDF, vous pouvez invoicer partout en UE.
Les PME BTP qui anticipent maintenant (début 2026) ont un avantage compétitif : zéro latence opérationnelle en juin. Les autres vivront un déni jusqu'à avril, puis une panique.
Si vous buildez un SaaS BTP, exposer Factur-X natif dans votre UI devient un argument de vente : "Aucune migration, conformité garantie en 2026." Anodos, par exemple, embarque cette capacité dès 2025 pour ses clients.
Olivier Ebrahim, fondateur d'Anodos — SaaS gestion de chantier. Passionné par l'interopérabilité des systèmes BTP et les formats qui survivent 10 ans.
Top comments (0)