Générer un Factur-X en PHP
Beaucoup d'applications de facturation françaises tournent en PHP, et produisent aujourd'hui un PDF classique via une bibliothèque de rendu. Passer à Factur-X ne consiste pas à changer de gabarit : il faut produire un PDF/A-3 qui embarque un XML CII valide au profil EN 16931. Voici la voie la plus courte, en cURL, sans dépendance Composer.
Ce qui ne suffit pas
Trois raccourcis souvent tentés, et pourquoi ils échouent :
| Raccourci | Pourquoi ça ne passe pas |
|---|---|
| Joindre le XML au PDF avec une bibliothèque de rendu classique | Le fichier obtenu est un PDF ordinaire, pas un PDF/A-3 : l'archivage et la déclaration de la pièce jointe ne sont pas conformes |
| Écrire le CII « à la main » avec du templating | Le XSD impose un ordre d'éléments et des cardinalités stricts ; le moindre écart est un rejet |
| S'arrêter au XSD | Les règles métier (totaux, ventilation de TVA, mentions légales) relèvent du Schematron, qui exige un moteur XSLT 2.0+ — hors de portée de l'extension XSL de PHP |
Générer une facture
Seule l'extension ext-curl est requise, présente par défaut
sur la plupart des distributions PHP.
<?php
const CONFACTURA_API = 'https://confactura.fr';
const CONFACTURA_KEY = 'df_live_…'; // POST /v1/signup pour en obtenir une
function confacturaGenerate(array $invoice): string
{
$ch = curl_init(CONFACTURA_API . '/v1/invoices/facturx');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . CONFACTURA_KEY,
],
CURLOPT_POSTFIELDS => json_encode($invoice, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 422) {
// Facture non conforme : rapport détaillé, aucun PDF produit.
$report = json_decode($body, true);
throw new DomainException(json_encode($report['errors'], JSON_UNESCAPED_UNICODE));
}
if ($status >= 400) {
throw new RuntimeException("Confactura API error {$status}: {$body}");
}
return $body; // PDF/A-3b brut, XML CII embarqué
}
$invoice = [
'number' => '2026-0042',
'issue_date' => '2026-09-01',
'due_date' => '2026-09-30',
'seller' => [
'name' => 'Ma Société',
'address_line' => '1 rue X',
'postcode' => '75001',
'city' => 'Paris',
'siren' => '123456789',
'vat_id' => 'FR32123456789',
'electronic_address'=> '[email protected]',
],
'buyer' => [
'name' => 'Client SARL',
'address_line' => '2 av. Y',
'postcode' => '69001',
'city' => 'Lyon',
'electronic_address'=> '[email protected]',
],
'lines' => [
['description' => 'Presta dev', 'quantity' => 5, 'unit_price' => '450.00'],
],
];
file_put_contents('facture-2026-0042.pdf', confacturaGenerate($invoice));
Le 422 est une réponse métier, pas une panne
Une facture qui ne passe pas XSD + Schematron n'est jamais livrée. Traitez le 422 comme un cas normal — à remonter à l'utilisateur qui a saisi la facture — et non comme une erreur serveur à retenter. Le corps de la réponse liste les règles en échec, une par une.
Les montants en chaînes, pas en float
'unit_price' => '450.00' et non 450.00 : la
représentation flottante fait dériver les totaux au centime, et le
Schematron vérifie l'égalité exacte entre la somme des lignes et le total
du document (BR-CO-10).
C'est la première cause de rejet en PHP comme ailleurs.
Les champs que la réforme française rend obligatoires
electronic_addresssur le vendeur et l'acheteur (BR-FR-12/BR-FR-13) — adresse de routage, pas un contact commercial ;vat_idcôté vendeur dès qu'une ligne est en TVA standard (BR-S-02) ;- un
sirenvalide côté vendeur.
Les mentions légales 2026 sont ajoutées côté serveur : voir
BR-FR-05.
Vérifier le résultat
Déposez le PDF produit sur le
validateur Factur-X en ligne, ou automatisez-le —
/v1/validate est gratuit, anonyme et ne consomme aucun quota :
<?php
$ch = curl_init(CONFACTURA_API . '/v1/validate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile('facture-2026-0042.pdf', 'application/pdf'),
],
]);
$report = json_decode(curl_exec($ch), true);
curl_close($ch);
var_dump($report['valid'], $report['errors']);
Essayer maintenant
20 documents par mois gratuits, sans carte bancaire. La clé API est créée en une requête.
Obtenir ma clé API Valider un PDF existant