« 1 dans le panier » sur un produit jamais ajouté : WooCommerce 11.3 règle le cas des lots
Prenons une boutique de mobilier de jardin. Quand un client ajoute une table, un snippet maison ajoute automatiquement le kit de fixation qui va avec. Le client revient sur la grille produits, et le bouton du kit affiche « 1 dans le panier ». Il ne l’a jamais ajouté. Au mieux, il se demande ce qui s’est passé. Au pire, il croit à un doublon, retire le kit, et reçoit une table impossible à monter.
Le même défaut touche toutes les extensions de lots, de produits composés ou de ventes forcées sur une boutique en blocs. WooCommerce 11.3 donne enfin un moyen de dire « cette ligne appartient à une autre ». Voici ce qui change, et comment l’appliquer à vos propres ventes liées.
D’où vient le faux compteur
Le compteur en question est celui du bloc Bouton produit, utilisé dans les grilles en blocs (bloc Collection de produits) et à l’intérieur du bloc Ajouter au panier avec options. Si votre boutique tourne sur un thème FSE, c’est lui qui s’affiche sous chaque produit.
Pour calculer « N dans le panier », le bloc additionne les lignes du panier qui correspondent au produit. Le cœur de WooCommerce n’a aucun moyen de savoir qu’une ligne a été ajoutée par une extension pour le compte d’une autre : pour lui, une ligne est une ligne, que le client l’ait choisie ou non. La seule façon de corriger le compteur, c’est que l’extension déclare elle-même la relation.
La 11.3 corrige au passage un second défaut. Quand un même produit occupait plusieurs lignes du panier (deux personnalisations différentes, par exemple), le bouton n’affichait que la quantité de la première ligne. Il additionne désormais toutes les lignes.
Le nouveau filtre de WooCommerce 11.3
WooCommerce 11.3 ajoute le filtre woocommerce_store_api_cart_item_parent_item_key, et un champ parent_item_key dans les lignes du panier renvoyées par la Store API. Ce champ est en lecture seule et vaut null par défaut. Quand il contient la clé d’une autre ligne, le Bouton produit ne compte plus cette ligne.
Le filtre s’exécute une fois par ligne, à chaque réponse de la Store API. Il reçoit la valeur courante (null, ou ce qu’une autre extension a déjà renvoyé) et le tableau de la ligne. Pour une ligne enfant, vous renvoyez la clé de ligne du parent. Attention, il s’agit bien de la clé renvoyée par WC()->cart->add_to_cart(), une chaîne du genre a87ff679a2f3e71d..., et pas l’ID du produit parent. Pour toutes les autres lignes, vous renvoyez la valeur reçue sans y toucher : si vous renvoyez null systématiquement, vous effacez la déclaration d’une autre extension.
WooCommerce fait aussi le ménage tout seul. Une clé qui ne correspond à aucune ligne, ou qui est celle de la ligne elle-même, est remplacée par null. Et le champ n’est renseigné que tant que le parent est dans le panier : si le client retire la table, le kit redevient une ligne autonome et compte à nouveau. Inutile de nettoyer la clé stockée.
Qui est concerné
Toutes les extensions qui ajoutent des lignes au panier pour le compte d’une autre : lots (bundles), produits composés, « mix and match », ventes forcées, produits chaînés, blocs « souvent achetés ensemble ». Pour les extensions du marché, c’est à leur éditeur d’adopter le filtre. Le test est rapide : ajoutez le produit parent au panier, puis regardez le bouton d’un produit enfant sur une grille en blocs. S’il affiche « 1 dans le panier », l’extension n’a pas encore suivi.
Pour les produits offerts et les ventes additionnelles, la réponse est moins tranchée. Un cadeau ajouté automatiquement peut très bien compter comme « dans le panier », c’est un choix d’interface plus qu’une erreur.
Ne rien faire ne casse rien : les lignes continuent d’être comptées comme aujourd’hui. Mais chez mes clients, ce genre de logique est rarement une extension du marché. C’est un snippet de vingt lignes écrit il y a trois ans, que personne ne viendra mettre à jour. C’est celui-là qu’il faut reprendre.
Une vente liée maison, déclarée proprement
Reprenons la table et son kit de fixation. Trois morceaux : ajouter le kit en mémorisant la clé de la table, déclarer la relation à la Store API, et retirer le kit si la table quitte le panier (WooCommerce ne le fait pas pour vous, il se contente de rendre le kit autonome).
/**
* Vente liée : produit => accessoire ajouté automatiquement.
* Adaptez les ID à votre catalogue.
*/
const WBA_VENTES_LIEES = array(
123 => 456, // Table de jardin => kit de fixation.
);
const WBA_CLE_PARENT = '_wba_parent_key';
// 1. Ajout du parent : on ajoute l'accessoire en mémorisant la clé de ligne du parent.
add_action( 'woocommerce_add_to_cart', function ( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
if ( isset( $cart_item_data[ WBA_CLE_PARENT ] ) ) {
return; // C'est l'accessoire lui-même, on s'arrête là.
}
$accessoire_id = WBA_VENTES_LIEES[ $product_id ] ?? 0;
if ( ! $accessoire_id ) {
return;
}
WC()->cart->add_to_cart( $accessoire_id, $quantity, 0, array(), array( WBA_CLE_PARENT => $cart_item_key ) );
}, 10, 6 );
// 2. Déclaration à la Store API (WooCommerce 11.3+).
add_filter( 'woocommerce_store_api_cart_item_parent_item_key', function ( $parent_item_key, $cart_item ) {
return $cart_item[ WBA_CLE_PARENT ] ?? $parent_item_key;
}, 10, 2 );
// 3. Le parent quitte le panier : on retire ses accessoires.
add_action( 'woocommerce_cart_item_removed', function ( $removed_key, $cart ) {
foreach ( $cart->get_cart() as $key => $item ) {
if ( ( $item[ WBA_CLE_PARENT ] ?? '' ) === $removed_key ) {
$cart->remove_cart_item( $key );
}
}
}, 10, 2 );
Quelques détails qui comptent. La clé du parent fait partie des données de la ligne du kit, donc WooCommerce la garde en session sans rien faire de plus. Elle entre aussi dans le calcul de l’identifiant de ligne : un kit ajouté par le client lui-même, en plus de celui de la table, occupe une ligne séparée, et c’est uniquement celle-là que le bouton comptera.
Les deux actions se déclenchent aussi quand l’ajout ou la suppression passe par la Store API, donc le code marche avec les boutons en blocs comme avec les pages classiques. Sur une version antérieure à 11.3, le filtre n’est jamais appelé : le reste du code fonctionne, seul le compteur reste faux.
Une limite à connaître, et je l’ai vérifiée dans le code de WooCommerce. Si le client ajoute une deuxième table, la ligne existante passe à 2, mais le kit ne suit pas toujours : le panier classique redéclenche woocommerce_add_to_cart dans ce cas, la Store API non (elle met à jour la quantité et s’arrête là). Même chose quand le client modifie la quantité depuis la page panier. Si vos ventes liées doivent rester synchronisées, c’est un chantier à part, avec ses propres règles de validation (j’en parlais dans l’article sur les limites de quantité du panier en blocs).
Pourquoi s’y mettre maintenant
Aujourd’hui, seul le Bouton produit lit ce champ. L’équipe WooCommerce indique que les blocs Panier et Mini-panier pourraient s’en servir plus tard pour afficher les enfants sous leur parent. Rien n’est promis, mais une relation déclarée aujourd’hui sera prête le jour où ça arrive, et le compteur est juste dès maintenant.
Concrètement
Cherchez dans votre thème et vos extensions maison les appels à add_to_cart() déclenchés depuis un autre ajout au panier (un grep -rn "add_to_cart(" wp-content/themes wp-content/plugins/mon-extension suffit à les repérer). Chacun est un candidat. Passez en 11.3 sur une préproduction, mémorisez la clé du parent, branchez le filtre, et refaites le test de la grille. L’annonce complète est sur le blog développeurs de WooCommerce.
Vous avez des lots ou des ventes liées codés maison et vous voulez savoir s’ils tiennent la route avec les blocs Panier et Commande ? Écrivez-moi.
Besoin d'un audit de votre boutique WooCommerce ?
Je vous envoie un diagnostic personnalisé avec les points à améliorer en priorité. Gratuit, sans engagement.