vendredi 25 septembre 2026

À propos

Contact

Tests

Simuler un webhook WooCommerce entrant, sans vrai serveur de paiement

Rejouer une charge utile réaliste de webhook permet de vérifier le traitement d'une commande sans dépendre d'un prestataire de paiement tiers pendant les tests.

Par Clément Hadrot • 3 décembre 2021 • 5 min de lecture • Aucun commentaire
Simuler un webhook WooCommerce entrant, sans vrai serveur de paiement

Un client vendant des abonnements numériques via WooCommerce et un prestataire de paiement tiers nous a demandé de fiabiliser le traitement des webhooks de confirmation de paiement, après un incident où une commande était restée bloquée « en attente » malgré un paiement bien débité. Reproduire le problème à la main impliquait de déclencher un vrai paiement de test à chaque essai, avec les délais du prestataire — un cycle de plusieurs minutes par tentative, incompatible avec une correction rapide.

La solution retenue a consisté à capturer une charge utile réelle de webhook une seule fois, en environnement de test du prestataire, puis à la rejouer autant de fois que nécessaire directement contre le point de terminaison REST du site, sans jamais solliciter à nouveau le vrai serveur de paiement.

Capturer une charge utile réaliste

La première étape, réalisée une seule fois, consiste à déclencher un vrai webhook de test depuis le tableau de bord du prestataire et à en enregistrer le contenu JSON exact, y compris ses en-têtes de signature :

{
  "event": "payment.succeeded",
  "payment_id": "pay_test_8f21ac",
  "order_reference": "1042",
  "amount": 4900,
  "currency": "EUR",
  "signature": "t=1638547200,v1=3a9f21..."
}

Rejouer la charge utile via WP_REST_Request

Plutôt que d’envoyer une vraie requête HTTP à l’URL publique du webhook, le test construit directement un objet WP_REST_Request et l’envoie au serveur REST interne de WordPress — plus rapide et totalement isolé du réseau :

class Test_Webhook_Paiement extends WP_UnitTestCase {

    public function test_paiement_reussi_marque_commande_payee(): void {
        $commande_id = $this->creer_commande_en_attente(1042);

        $requete = new WP_REST_Request('POST', '/mon-plugin/v1/webhook-paiement');
        $requete->set_header('Content-Type', 'application/json');
        $requete->set_header('X-Signature', 't=1638547200,v1=3a9f21...');
        $requete->set_body(file_get_contents(__DIR__ . '/fixtures/webhook-payment-succeeded.json'));

        $reponse = rest_get_server()->dispatch($requete);

        $this->assertSame(200, $reponse->get_status());

        $commande = wc_get_order($commande_id);
        $this->assertSame('processing', $commande->get_status());
    }
}
L'essentiel à retenir : Capturer une charge utile réelle une fois, la rejouer ensuite ; Simuler la requête HTTP entrante avec WP_REST_Request ; Couvrir les signatures invalides et les retards de traitement

La fixture JSON, stockée dans un fichier séparé plutôt que codée en dur dans le test, se rapproche visuellement du format exact que produit le prestataire — un détail qui compte pour repérer rapidement une divergence de structure lors d’une mise à jour de leur API.

Vérifier le rejet d’une signature invalide

Un test tout aussi important, souvent oublié : s’assurer que le point de terminaison refuse une requête dont la signature ne correspond pas, pour éviter qu’un tiers malveillant ne puisse forger de fausses confirmations de paiement :

public function test_signature_invalide_rejetee(): void {
    $requete = new WP_REST_Request('POST', '/mon-plugin/v1/webhook-paiement');
    $requete->set_header('X-Signature', 't=1638547200,v1=signature-falsifiee');
    $requete->set_body(file_get_contents(__DIR__ . '/fixtures/webhook-payment-succeeded.json'));

    $reponse = rest_get_server()->dispatch($requete);

    $this->assertSame(401, $reponse->get_status());
}

Traiter le cas d’une commande déjà traitée

Les prestataires de paiement garantissent rarement une livraison unique de chaque webhook : un même événement peut arriver deux fois. Le traitement doit donc être idempotent, et un test dédié vérifie qu’un second envoi de la même charge utile ne double pas les effets (envoi d’un deuxième e-mail de confirmation, par exemple) :

  • Rejouer deux fois exactement la même charge utile de webhook.
  • Vérifier que le statut de la commande reste cohérent après le second appel.
  • Vérifier qu’un seul e-mail de confirmation a été programmé, pas deux.

Un webhook non idempotent est une bombe à retardement : le jour où le prestataire de paiement retente un envoi après un timeout réseau, deux e-mails de confirmation partent, et le support client doit gérer la confusion.

Simuler aussi un retard ou une commande introuvable

Un dernier scénario mérite un test explicite : la référence de commande contenue dans le webhook ne correspond à aucune commande existante, par exemple parce que le webhook arrive avant que la commande locale ne soit créée dans certains scénarios de checkout asynchrone. Le point de terminaison doit répondre par un code HTTP explicite plutôt que de générer une erreur fatale PHP, pour que le prestataire sache qu’il doit retenter l’envoi plus tard :

public function test_commande_introuvable_repond_404_pour_retry(): void {
    $requete = new WP_REST_Request('POST', '/mon-plugin/v1/webhook-paiement');
    $requete->set_body(json_encode(['order_reference' => '999999']));

    $reponse = rest_get_server()->dispatch($requete);

    $this->assertSame(404, $reponse->get_status());
}

En résumé

Simuler un webhook de paiement en rejouant une charge utile capturée une fois permet de tester exhaustivement le traitement d’une commande — succès, signature invalide, doublon, référence inconnue — sans jamais dépendre de la disponibilité ni du temps de réponse d’un serveur tiers. Ce sujet ne couvre volontairement pas le tunnel de commande complet côté visiteur, qui relève d’un test de bout en bout distinct, déjà traité séparément.

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