# Antipatterns d’abonnement : facturer deux fois après un changement de carte

> Un renouvellement déclenche parfois deux prélèvements après la mise à jour d'un moyen de paiement. Repérer les antipatterns qui provoquent ce doublon et comment les corriger.

- Auteur : WordPress Développement
- Publié le : 2024-02-24
- Mis à jour le : 2024-02-24
- Catégorie : E-commerce
- URL : https://www.wpmoderne.fr/ecommerce/antipatterns-abonnement-double-facturation-changement-carte/

## L’essentiel

- Deux hooks non coordonnés déclenchent souvent le même prélèvement
- Le changement de carte ne doit jamais lancer un renouvellement immédiat
- Un verrou applicatif simple évite la majorité des doublons

Prélèvement dupliqué détecté : abonnement #4821, deux transactions de 39 € à quatre minutes d'écart, référence de renouvellement identique. Ce type d'entrée dans le journal de la passerelle de paiement revient régulièrement sur les boutiques qui vendent des abonnements récurrents, presque toujours peu après qu'un client a mis à jour sa carte bancaire expirée. Le symptôme est net et reproductible : deux transactions identiques, à quelques minutes d'écart, sur le même abonnement. Ce n'est pas un hasard, mais une conséquence directe de la façon dont certains développements réagissent à l'événement de mise à jour de moyen de paiement.

Ce billet ne traite pas la gestion des échecs de paiement récurrents en général — la relance après carte refusée — mais un cas précis : le doublon qui survient juste après une correction de moyen de paiement réussie. Voici les antipatterns qui le provoquent et la façon de les corriger.

## Antipattern n° 1 : relancer un renouvellement dès la mise à jour de carte

Le code fautif le plus fréquent écoute le hook `woocommerce_subscription_payment_method_updated` pour, en toute bonne intention, vérifier immédiatement que la nouvelle carte fonctionne en déclenchant un renouvellement manuel via `$subscription->maybe_charge_renewal()` ou une commande de retry personnalisée. Le problème apparaît quand Action Scheduler, en parallèle, exécute déjà la tâche planifiée du renouvellement normal prévu à échéance proche. Résultat : deux tentatives de prélèvement se chevauchent sur le même abonnement, l'une déclenchée par le code métier, l'autre par le planificateur natif.

```
// À éviter : relance immédiate sur mise à jour de moyen de paiement
add_action( 'woocommerce_subscription_payment_method_updated', function( $subscription ) {
    $subscription->maybe_charge_renewal(); // déclenche un second prélèvement possible
} );
```

## Antipattern n° 2 : ignorer le statut de la dernière tentative de paiement

> L'essentiel à retenir : Deux hooks non coordonnés déclenchent souvent le même prélèvement ; Le changement de carte ne doit jamais lancer un renouvellement immédiat ; Un verrou applicatif simple évite la majorité des doublons

Un deuxième défaut courant consiste à ne pas vérifier si une tentative de paiement est déjà en cours de traitement avant d'en lancer une nouvelle. WooCommerce Subscriptions stocke un statut de renouvellement sur la commande associée ; ignorer ce statut avant de relancer un prélèvement revient à ne jamais poser de verrou, ce qui ouvre la porte à une course entre deux processus, qu'il s'agisse d'Action Scheduler ou d'un webhook du prestataire de paiement arrivé en double.

Le correctif consiste à toujours vérifier l'état de la dernière commande de renouvellement avant d'en créer une nouvelle :

```
function wpm_peut_relancer_paiement( $subscription ) {
    $derniere_commande = $subscription->get_last_order( 'all' );

    if ( $derniere_commande && $derniere_commande->has_status( array( 'pending', 'processing' ) ) ) {
        return false; // un paiement est déjà en cours, ne pas en lancer un second
    }

    return true;
}
```

## Le correctif : séparer validation de carte et renouvellement

La bonne pratique consiste à distinguer clairement deux actions qui n'ont rien à voir : valider qu'une carte est fonctionnelle, et déclencher un renouvellement. La validation peut se faire via une autorisation à montant nul ou minimal proposée par la plupart des passerelles, sans jamais capturer de somme réelle. Le renouvellement, lui, reste uniquement du ressort du planificateur natif de WooCommerce Subscriptions, à la date d'échéance prévue.

- Ne jamais déclencher `maybe_charge_renewal()` depuis un hook de mise à jour de moyen de paiement.
- Utiliser l'API de validation de carte de la passerelle (souvent une pré-autorisation à 0 ou 1 euro) pour confirmer que le nouveau moyen de paiement est accepté.
- Laisser Action Scheduler gérer seul la date de prochain prélèvement, sans intervention manuelle parallèle.

## Vérifier la présence du doublon en base

Pour confirmer qu'un abonnement a bien été touché par ce bug, une requête WP-CLI permet de lister les commandes de renouvellement créées à quelques minutes d'intervalle sur un même abonnement :

```
wp wc shop_subscription list --status=active --format=ids | \
xargs -I{} wp post list --post_type=shop_order --meta_key=_subscription_renewal --meta_value={} --format=table
```

Un écart de quelques minutes entre deux commandes portant la même référence de renouvellement est un signe fiable du doublon décrit ici, à distinguer d'un vrai problème de webhook dupliqué côté prestataire, qui se traiterait différemment.

## Prévention durable

Au-delà du correctif ponctuel, il est utile d'ajouter un verrou applicatif générique autour de toute tentative de prélèvement sur un abonnement, sous forme de transient de courte durée portant l'identifiant de l'abonnement. N'importe quel code qui tenterait de lancer un second renouvellement pendant que le verrou est posé serait alors bloqué, quelle que soit son origine — planificateur natif, code métier ou webhook externe.

> Un verrou de trente secondes sur l'identifiant d'abonnement suffit largement : le temps qu'une passerelle de paiement réponde à une requête est presque toujours inférieur à ce délai.

## En résumé

Le doublon de prélèvement après changement de carte vient presque toujours d'un code métier qui confond validation de moyen de paiement et déclenchement de renouvellement. Séparer ces deux responsabilités, vérifier systématiquement le statut de la dernière commande avant toute relance, et poser un verrou applicatif simple suffisent à éliminer ce type d'antipattern durablement.
