Si des commandes WooCommerce s’accumulent en on-hold ou en attente de paiement, ou si un client dit n’avoir jamais reçu son courriel de confirmation, le statut de la commande vous dit pourquoi. Chaque statut est un état dans lequel un acteur précis a fait passer la commande, que ce soit une passerelle de paiement, un mode de paiement hors ligne ou une personne dans l’administration, et chaque changement laisse une note sur la commande. Les commandes bloquées en on-hold y ont presque toujours été mises par un mode hors ligne comme le virement bancaire, ou par un paiement par carte dont le rappel de confirmation n’est jamais arrivé. Une pile de commandes en attente de paiement signifie que des clients ont atteint la page de paiement et sont partis. Un courriel manquant veut d’habitude dire que la commande n’a jamais franchi le changement de statut qui l’envoie, alors lisez les notes de la commande avant de déboguer votre configuration de courriel.
Ce que vous allez accomplir
- Lire le statut de n’importe quelle commande et nommer le mécanisme exact qui l’a posé, au lieu de deviner.
- Expliquer pourquoi les commandes on-hold et en attente de paiement s’accumulent, et configurer l’annulation automatique qui vide les secondes.
- Associer chaque transition de statut aux courriels client et administrateur qu’elle envoie — et à ceux qu’elle n’envoie pas.
- Enregistrer un statut personnalisé correctement, et connaître les quatre choses qu’il brise en silence.
- Faire l’audit des statuts de la boutique en ligne de commande, autant en stockage HPOS qu’en stockage historique.
Prérequis
WooCommerce 8.0 ou plus récent sur WordPress 6.4+, PHP 8.1+. Un accès à l’interpréteur de commandes avec WP-CLI disponible sous wp, exécuté comme utilisateur du site depuis la racine WordPress. Les sous-commandes wp wc utilisées ici passent par les contrôleurs REST, qui vérifient les capacités : elles exigent donc un indicateur --user= nommant un administrateur. Tous les exemples utilisent --user=1 — remplacez-le par votre propre identifiant d’administrateur.
Un seul détail de stockage change la moitié des requêtes. Le stockage haute performance des commandes (HPOS) garde les commandes dans une table dédiée wp_wc_orders, avec une simple colonne status. Sans HPOS, les commandes restent des articles dans wp_posts et le statut vit dans post_status. Les notes de commande, elles, ne changent pas : ce sont des commentaires dans wp_comments avec comment_type = 'order_note' dans les deux modes. Vérifiez dans quel mode vous êtes avant de lancer quoi que ce soit.
wp option get woocommerce_custom_orders_table_enabled
wp config get table_prefix
Attendez-vous à yes (HPOS actif) ou no, suivi du préfixe de vos tables. Si vous ignorez comment la boutique s’est retrouvée dans son mode actuel, ou si vous préparez la bascule, consultez le guide de migration HPOS.
Comment ça fonctionne
En interne, chaque statut de base est stocké avec un préfixe wc- : wc-pending, wc-processing, wc-on-hold, wc-completed, wc-cancelled, wc-refunded, wc-failed et wc-checkout-draft. Ce préfixe existe parce que les statuts ont d’abord été enregistrés comme des statuts d’article WordPress et devaient éviter la collision avec pending, draft et publish. C’est un détail d’implémentation qu’il faut connaître de toute façon : $order->get_status() renvoie le slug sans le préfixe, tandis que la colonne en base, register_post_status() et le filtre wc_order_statuses l’utilisent tous avec. Comparer directement les deux formes est de loin le bogue le plus fréquent dans le code qui manipule les statuts.
Trois acteurs différents déplacent les commandes :
La passerelle. Une passerelle en ligne crée la commande en pending, envoie le client ailleurs, puis appelle WC_Order::payment_complete() quand son rappel, son webhook ou son IPN confirme l’argent. Cette méthode ne code pas processing en dur : elle demande à la commande si quelque chose exige une intervention humaine. Si toutes les lignes sont à la fois virtuelles et téléchargeables, la commande passe directement en completed et les liens de téléchargement s’ouvrent ; sinon, elle passe en processing. C’est pourquoi une boutique purement numérique ne voit jamais de processing, et pourquoi une boutique qui ajoute un seul article expédiable à un lot numérique en voit soudainement.
Les passerelles hors ligne. Le virement bancaire, le chèque et le paiement à la livraison ne peuvent rien confirmer : ils posent donc un statut à la main au moment de la caisse. Le virement et le chèque posent on-hold, ce qui réduit le stock et attend que vous confirmiez la réception de l’argent. Le paiement à la livraison pose processing — sauf si la commande contient un article téléchargeable, auquel cas il pose on-hold pour ne pas libérer les fichiers avant paiement. Cette seule bifurcation explique la plupart des billets « pourquoi le paiement à la livraison est-il parfois en on-hold ».
Vous, dans l’administration. Changer le statut depuis l’écran de commande ou par une action groupée déclenche exactement les mêmes points d’accroche de transition qu’une passerelle : donc les mêmes courriels et les mêmes mouvements de stock. Il n’existe pas de changement manuel « discret ».
Le stock suit le statut, pas le panier. WooCommerce réduit le stock quand une commande atteint on-hold, processing ou completed, et le remet quand une commande passe en cancelled ou revient en pending. Une métadonnée note que la réduction a eu lieu, pour qu’un passage de processing à completed ne décrémente pas deux fois. Le corollaire compte : une commande qui dort en pending ne retient aucun stock, au-delà de la courte ligne de réservation — c’est le mécanisme derrière les conditions de course de survente.
Pas à pas
1. Obtenez la liste des statuts depuis le site en fonctionnement. Des extensions en ajoutent, et le menu déroulant de l’administration est le mauvais endroit où vérifier.
wp eval 'print_r( wc_get_order_statuses() );'
Vous devriez voir un tableau indexé par slug préfixé — wc-pending, wc-processing, wc-on-hold, wc-completed, wc-cancelled, wc-refunded, wc-failed — associé à des libellés traduits. Notez que wc-checkout-draft en est délibérément absent : c’est un statut d’article enregistré, utilisé par le bloc Commande pour conserver une commande que le client n’a pas encore soumise, et un nettoyage planifié supprime les brouillons périmés. Il ne devrait jamais apparaître dans vos rapports.
2. Comptez les commandes par statut. En HPOS :
wp db query "SELECT status, COUNT(*) AS orders FROM wp_wc_orders GROUP BY status ORDER BY orders DESC;"
En stockage historique :
wp db query "SELECT post_status, COUNT(*) AS orders FROM wp_posts WHERE post_type = 'shop_order' GROUP BY post_status ORDER BY orders DESC;"
Les deux affichent un tableau à deux colonnes. Une boutique en santé montre la majorité des commandes en wc-completed, une file de travail en wc-processing, et de petits volumes ailleurs. Un décompte à quatre chiffres en wc-pending est normal pour une boutique achalandée sans annulation automatique ; à quatre chiffres en wc-on-hold, ça ne l’est pas, et ça mène à l’étape 4.
3. Listez les commandes bloquées avec assez de contexte pour poser un diagnostic. Le moyen de paiement est presque toujours l’indice décisif.
wp wc shop_order list --user=1 --status=on-hold --fields=id,date_created,total,payment_method_title --format=table
Le succès ressemble à un tableau de commandes. Si toutes les lignes affichent le même moyen hors ligne, la boutique fonctionne comme prévu et vous avez simplement des virements non confirmés. Si les lignes montrent une passerelle par carte, un rappel n’est pas arrivé : passez à l’étape 5.
4. Traitez la pile des « pending ». Les commandes impayées en pending n’expirent jamais d’elles-mêmes. Le champ « Rétention de stock (minutes) » sous WooCommerce > Réglages > Produits > Inventaire planifie une tâche cron qui annule les commandes impayées plus vieilles que ce nombre de minutes, en libérant le stock réservé. Elle ne touche que les commandes nées de la caisse normale : les importations et les commandes créées par l’API REST sont épargnées.
wp option get woocommerce_manage_stock
wp option get woocommerce_hold_stock_minutes
Attendez-vous à yes puis à un nombre comme 60. Une valeur vide ou 0 signifie que l’annulation automatique est désactivée. La régler ne fait que la moitié du travail : vérifiez que l’événement est réellement planifié, car une boutique dont le WP-Cron est désactivé sans cron système ne l’exécutera jamais.
wp cron event list --fields=hook,next_run_relative | grep woocommerce_cancel_unpaid_orders
Vous voulez une ligne avec une prochaine exécution dans le futur. Aucune sortie signifie que l’événement manque ; réenregistrer la page des réglages d’inventaire le replanifie.
5. Lisez les notes de commande. Les notes sont le journal d’audit des changements de statut : chaque rappel de passerelle, chaque changement manuel, chaque ajustement de stock en écrit une.
wp db query "SELECT comment_date, comment_author, comment_content FROM wp_comments WHERE comment_type = 'order_note' AND comment_post_ID = 1234 ORDER BY comment_date;"
Remplacez 1234 par l’identifiant de la commande. Une commande par carte coincée en on-hold ou pending, dont les notes s’arrêtent juste après « en attente de paiement », n’a jamais reçu son rappel. Une commande qui montre l’arrivée du rappel suivie d’une erreur est un tout autre problème — le journal de la passerelle en détient généralement le détail.
6. Déplacez une commande volontairement. Une fois l’argent confirmé, faites la transition plutôt que de modifier la base.
wp wc shop_order update 1234 --status=processing --user=1
La commande affiche la commande mise à jour. Comme elle passe par le mutateur normal, elle déclenche les points d’accroche de transition, envoie le courriel client « En traitement » et réduit le stock si ce n’est pas déjà fait — soit exactement ce que vous voulez, et exactement pourquoi un UPDATE SQL direct sur la colonne de statut est le mauvais outil.
Valider que ça fonctionne
C’est la carte des transitions que vous validez réellement, parce que c’est elle qui explique le comportement des courriels. Confirmez quels statuts la boutique considère comme payés :
wp eval 'print_r( wc_get_is_paid_statuses() );'
La sortie par défaut donne processing et completed, sans préfixe. Toute autre valeur signifie qu’une extension a filtré woocommerce_order_is_paid_statuses, et vos rapports s’en ressentiront.
Vérifiez ensuite le côté courriel. Les courriels sont accrochés à des points de transition comme woocommerce_order_status_pending_to_processing, pas à des statuts. Le courriel administrateur Nouvelle commande ne part que lorsqu’une commande passe de pending ou failed vers processing, completed ou on-hold. Le courriel client Commande en cours de traitement part à chaque entrée en processing. Commande terminée part à chaque entrée en completed. En attente ne part que depuis pending ou failed. Rien du tout n’est envoyé pour une commande qui se contente de naître en pending, et il n’existe aucun courriel client pour cancelled par défaut.
Cette carte a une conséquence pratique qu’il vaut mieux dire clairement : une commande créée directement en processing par un importateur, l’API REST ou une extension n’est jamais passée par pending, donc le courriel Nouvelle commande ne part jamais. La pile de courriel, elle, va très bien. Avant de passer un après-midi sur le SMTP, confirmez que la transition a eu lieu — la liste de vérification complète côté courriel se trouve dans courriel de nouvelle commande non reçu.
Enfin, validez un aller-retour complet sur une commande d’essai : passez-la, voyez-la atterrir en pending, complétez le paiement, confirmez qu’elle passe en processing et que le stock a bien baissé de la quantité commandée.
Dépannage
Toutes les commandes par carte restent en on-hold ou en pending. La passerelle ne rappelle pas. Vérifiez que l’URL de rappel enregistrée chez la passerelle correspond à l’URL actuelle du site (un déplacement de préproduction vers production en est la cause classique), que le point d’entrée n’est pas derrière une authentification HTTP ou une règle de pare-feu, et que le tableau de bord de la passerelle montre bien des tentatives de livraison. La plupart des passerelles permettent de rejouer un webhook, ce qui est le correctif propre : il emprunte le même chemin de code que le rappel en direct.
Les commandes atteignent processing mais la boutique ne reçoit jamais le courriel Nouvelle commande. Cherchez dans les notes la trace d’un état pending. Si la commande est née en processing, aucun point d’accroche de transition n’a été déclenché. Corrigez le code qui la crée pour qu’il crée en pending puis appelle payment_complete(), ou acceptez-le et notifiez autrement.
Un statut personnalisé casse les rapports. Enregistrer wc-awaiting-pickup tient en deux accroches :
add_action( 'init', function () {
register_post_status( 'wc-awaiting-pickup', array(
'label' => 'Awaiting pickup',
'public' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
'label_count' => _n_noop( 'Awaiting pickup (%s)', 'Awaiting pickup (%s)' ),
) );
} );
add_filter( 'wc_order_statuses', function ( $statuses ) {
$statuses['wc-awaiting-pickup'] = 'Awaiting pickup';
return $statuses;
} );
Ça le fait entrer dans le menu déroulant, et rien d’autre. Ce n’est pas un statut payé, donc les rapports de revenus l’ignorent ; aucun courriel n’est rattaché à une transition vers lui ; et les passerelles qui présument que processing veut dire payé ne le reconnaîtront pas. Ajoutez-le explicitement à l’ensemble des statuts payés si c’est bien ce que vous voulez dire, en utilisant le slug sans préfixe :
add_filter( 'woocommerce_order_is_paid_statuses', function ( $statuses ) {
$statuses[] = 'awaiting-pickup';
return $statuses;
} );
Réglez ensuite les statuts actionnables et exclus de la boutique sous WooCommerce > Réglages > Analyses, pour que le nouveau statut soit comptabilisé comme vous l’entendez.
Une passerelle n’apparaît pas à la caisse, donc aucune commande n’est créée. C’est une panne différente avec le même symptôme d’« aucune commande » : commencez plutôt par passerelle de paiement absente.
Les commandes remboursées affichent les mauvais totaux. Un remboursement complet pose refunded ; un remboursement partiel enregistre un objet de remboursement et laisse le statut tel quel, ce qui est correct mais déroutant. Voyez remboursements partiels et remise en stock.
Quand ce n’est pas la bonne approche
Les statuts sont une machine à états de flux de travail, pas un modèle de données. Si vous vous surprenez à ajouter six statuts personnalisés pour suivre les étapes d’un entrepôt, ce qu’il vous faut, ce sont des métadonnées de commande et un écran conçu pour ça : les statuts débordent dans les rapports, les courriels, les présomptions des passerelles et toutes les extensions qui interrogent les commandes. Modifier les statuts en masse pour « corriger » des données historiques est aussi une mauvaise idée : les transitions déclenchent les accroches de courriel et de stock, donc déplacer 800 vieilles commandes d’un coup peut envoyer 800 messages. Et si la vraie question est de savoir si l’argent est arrivé, c’est le tableau de bord de la passerelle qui fait foi ; les statuts WooCommerce ne reflètent jamais que ce que la passerelle a réussi à lui dire.
Références officielles : register_post_status() et la documentation WooCommerce sur la gestion des commandes.
Un WordPress qui rapporte vraiment mérite un opérateur — Stack Harbor opère cette pile sous WordPress Entreprise ; réservez une consultation d’infrastructure.