Le WordPress d'aujourd'hui, décodé pour les développeurs

E-commerce

Une file de génération de factures asynchrone pour une boutique à fort volume

Une architecture qui détache la génération de PDF du tunnel de commande WooCommerce, pour éviter qu'un pic de commandes ne ralentisse le passage en caisse.

Par Clément Hadrot • 25 octobre 2025 • 5 min de lecture • Aucun commentaire
Une file de génération de factures asynchrone pour une boutique à fort volume

Fatal error: Maximum execution time of 30 seconds exceeded — ce message, apparu dans les logs un vendredi de forte affluence, a suffi à convaincre l’équipe qu’il fallait sortir la génération de factures PDF du chemin critique du tunnel de commande. Le coupable n’était pas le paiement lui-même, mais la librairie de rendu PDF appelée de façon synchrone juste après la confirmation de commande.

Ce cas n’a rien d’exceptionnel. Beaucoup d’implémentations de facturation sur WooCommerce génèrent le PDF au moment même où le statut de commande passe à « payée », dans le même cycle de requête que celui vu par l’acheteur. Tant que le volume reste modeste, cela fonctionne. Dès qu’un pic de commandes arrive — lancement produit, opération commerciale — la génération PDF devient un goulot qui ralentit tout le monde, y compris les acheteurs dont la commande n’a rien à voir avec une facture complexe.

Pourquoi le couplage synchrone est un problème structurel

Le problème n’est pas la lenteur intrinsèque de la génération PDF, mais son couplage temporel avec le parcours d’achat. Tant que la génération de facture bloque la réponse HTTP renvoyée à l’acheteur, chaque commande paie le coût de la plus lente des opérations liées à cette commande, même si cette opération n’a aucune valeur immédiate pour l’acheteur : il n’a pas besoin du PDF dans la seconde, il en aura besoin quand il consultera son espace client ou son email de confirmation.

La solution ne consiste pas à optimiser la librairie de génération, mais à sortir cette tâche du chemin critique : détacher, mettre en file, traiter en tâche de fond, puis notifier une fois le document prêt.

Une architecture en trois étages

L'essentiel à retenir : Ne jamais générer un PDF pendant la requête de paiement ; Une file avec reprise sur échec, pas un cron muet ; Notifier plutôt que faire attendre l'acheteur

L’architecture retenue repose sur trois éléments distincts, chacun responsable d’une seule chose :

Commande payée (hook woocommerce_order_status_completed)
        │
        ▼
Planification d'une tâche Action Scheduler
        │  (payload : ID de commande uniquement)
        ▼
Worker de génération PDF (asynchrone, hors requête HTTP)
        │
        ├── Succès → PDF stocké, méta commande mise à jour
        └── Échec → nouvelle tentative planifiée, jusqu'à un plafond

Le point clé de ce schéma est que la tâche planifiée ne transporte que l’identifiant de commande, jamais les données de facturation elles-mêmes. Cela évite qu’une modification de commande entre la planification et l’exécution ne produise une facture basée sur des données obsolètes.

Planifier la tâche sans bloquer la réponse

add_action( 'woocommerce_order_status_completed', function( $order_id ) {
    if ( ! as_next_scheduled_action( 'generer_facture_pdf', array( 'order_id' => $order_id ) ) ) {
        as_schedule_single_action(
            time() + 5,
            'generer_facture_pdf',
            array( 'order_id' => $order_id ),
            'facturation'
        );
    }
} );

Le délai de cinq secondes n’est pas arbitraire : il laisse le temps aux autres hooks liés au changement de statut de terminer leur exécution avant que le worker ne lise la commande, évitant ainsi de générer un document à partir d’un état intermédiaire de la commande.

Gérer l’échec sans perdre le client

Une file asynchrone qui échoue silencieusement est pire qu’une génération synchrone lente : au moins, dans le second cas, l’échec est visible immédiatement. Le worker doit donc distinguer deux catégories d’échec :

  • Échec transitoire (service de stockage temporairement indisponible) : nouvelle tentative automatique avec délai croissant
  • Échec définitif (données de commande incohérentes, TVA manquante) : arrêt des tentatives et alerte à l’équipe support, jamais de nouvelle tentative infinie

Cette distinction évite le piège classique d’une file qui retente indéfiniment une tâche vouée à échouer, consommant des ressources sans jamais produire de résultat.

Notifier plutôt que faire attendre

Côté acheteur, la page de confirmation de commande ne mentionne plus « génération de votre facture en cours » avec une roue qui tourne. Elle confirme la commande immédiatement et précise que la facture sera jointe à l’email de confirmation, envoyé dès que le document est prêt — en pratique, quelques secondes plus tard, mais sans que l’acheteur n’ait attendu cette durée sur la page.

Une tâche qui n’a pas besoin d’être terminée avant la réponse HTTP n’a rien à faire dans le cycle de la requête HTTP. C’est la seule règle qui compte pour décider ce qui part en file d’attente.

Ce que cette architecture ne couvre pas

Cette approche concerne exclusivement le détachement technique de la génération, pas la mise en forme des factures elles-mêmes : mentions légales, numérotation séquentielle, format du document. Ces questions relèvent d’un chantier distinct, une fois l’architecture de génération stabilisée.

Pour aller plus loin

Détacher la génération de factures d’une file d’attente asynchrone change la nature du problème : on passe d’une question de vitesse à une question de fiabilité de traitement. Le gain sur le temps de réponse du tunnel de commande est immédiat et mesurable ; la vraie difficulté se déplace vers la gestion des échecs et la garantie qu’aucune commande payée ne reste durablement sans facture générée.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi