DEV Community

Olivier EBRAHIM
Olivier EBRAHIM

Posted on

Factur-X 2026 : Guide d'implémentation pour les PME du BTP

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 :

  1. XML Schema complexe (2 MB de spec Afnor)
  2. PDF/A-3 embedding (nécessite une lib spéciale, pas juste iText/PDFBox)
  3. Signature numérique optionnelle mais recommandée (certificats, PKI)
  4. 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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

  1. 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
  2. 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
  3. 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"
  4. 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
  5. 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...
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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)