Fatal error: Uncaught Error: Call to a member function get_price() on null. C’est ce message, découvert dans les logs d’erreurs un lundi matin, qui a mis la puce à l’oreille d’une équipe technique gérant un site d’abonnements SaaS traduit en trois langues avec WPML. Le renouvellement automatique d’un abonnement avait échoué dans la nuit, pour un sous-ensemble précis de clients : ceux dont l’abonnement avait été souscrit sur la version traduite du plan tarifaire, après que ce plan avait été modifié côté langue source.
Ce cas est distinct des sujets déjà traités sur les taxes et zones d’expédition multilingues : il touche spécifiquement au cycle de renouvellement d’un abonnement WooCommerce Subscriptions, un mécanisme qui recharge le produit associé à chaque échéance pour recalculer le prix et déclencher la facturation.
Symptôme : un renouvellement qui échoue ou change de prix
Deux symptômes se manifestent, parfois ensemble. Le premier est un échec pur et simple du renouvellement, avec une erreur fatale dans les logs au moment où WooCommerce Subscriptions tente de récupérer l’objet produit associé à l’abonnement pour recalculer la ligne de commande. Le second, plus insidieux, est un renouvellement qui aboutit mais facture un prix différent de celui affiché au client lors de la souscription, sans qu’aucune erreur n’apparaisse.
Diagnostic : l’ID produit ne pointe plus vers la bonne traduction
WooCommerce Subscriptions stocke, au moment de la souscription, l’ID du produit WooCommerce associé à l’abonnement (méta _product_id sur la ligne de commande). Sur un site traduit avec WPML, chaque langue possède un produit avec un ID propre, reliés entre eux par la table de traduction de WPML (icl_translations). Si le produit source est supprimé et recréé plutôt que simplement modifié — un cas fréquent quand une équipe marketing refond une grille tarifaire —, l’ancien ID traduit devient orphelin : la traduction pointe encore vers un produit qui n’existe plus, tandis qu’un nouveau produit source a été créé sans jamais être relié à une nouvelle traduction.

Au moment du renouvellement, WooCommerce Subscriptions tente de charger le produit via wc_get_product( $product_id ) avec cet ID devenu orphelin. La fonction retourne false, et tout appel de méthode suivant sur cette valeur (get_price(), get_name()) provoque l’erreur fatale observée dans les logs. Quand le produit source a simplement été modifié sans être supprimé, la traduction reste valide mais le prix affiché peut différer si la synchronisation des champs personnalisés entre langues (gérée par les réglages de synchronisation WPML) n’a pas inclus le champ prix, un oubli fréquent car le prix est souvent perçu à tort comme une donnée qui n’a pas besoin de traduction.
Correctif : sécuriser la référence produit et vérifier la synchronisation
La première étape est corrective et immédiate : retrouver les abonnements dont le _product_id pointe vers un produit supprimé, via une requête qui croise la table wp_postmeta des abonnements avec la table wp_posts pour ne garder que les références orphelines. Chaque abonnement concerné doit être ré-associé manuellement au produit actuellement actif, via l’interface d’édition de l’abonnement dans WooCommerce, en sélectionnant à nouveau le bon produit dans la langue correspondant à celle du client.
add_action( 'woocommerce_subscription_renewal_payment_created', function( $renewal_order, $subscription ) {
foreach ( $subscription->get_items() as $item ) {
$product = $item->get_product();
if ( ! $product ) {
error_log( sprintf(
'Produit introuvable pour abonnement #%d, item #%d',
$subscription->get_id(),
$item->get_id()
) );
}
}
}, 10, 2 );
Ce hook ne corrige rien seul, mais il transforme une erreur fatale silencieuse en alerte exploitable avant que le renouvellement n’échoue en production, en journalisant le problème dès sa survenue plutôt que de laisser un client sans facture.
Prévention : ne jamais supprimer un produit source lié à des abonnements actifs
La vraie prévention se joue en amont, dans le processus éditorial de mise à jour des grilles tarifaires. Un produit lié à des abonnements actifs ne doit jamais être supprimé, même temporairement dans un aller-retour de sauvegarde-restauration mal maîtrisé. La bonne pratique est de dépublier (statut brouillon) plutôt que supprimer, ce qui conserve l’ID et la relation de traduction WPML intacts.
- Vérifier, avant toute suppression de produit, l’existence d’abonnements actifs liés via la table
wp_woocommerce_order_items - Passer le produit en brouillon plutôt que de le supprimer si des abonnements y sont rattachés
- Contrôler dans les réglages WPML que le champ prix fait bien partie des champs synchronisés ou volontairement non synchronisés selon la stratégie tarifaire par langue
- Ajouter une alerte de journalisation sur les échecs de récupération produit lors du renouvellement
En résumé
Ce type d’incident illustre un piège classique du multilingue appliqué à des mécanismes qui n’ont pas été pensés pour la traduction à l’origine : WooCommerce Subscriptions raisonne en ID de produit stable, alors qu’une refonte tarifaire mal exécutée sur un site multilingue peut casser cette stabilité sans qu’aucune alerte ne remonte avant le prochain cycle de facturation. Surveiller les logs de renouvellement et interdire la suppression de produits actifs reste la meilleure protection.