Chapitre 10.5 — Paiements & passerelles
⏱️ TL;DR — Une passerelle de paiement (payment gateway) est une classe qui étend
WC_Payment_Gateway: elle déclare ses réglages, affiche son moyen de paiement au checkout, et traite le paiement (process_payment). Deux modèles : redirection (le client va sur la page du prestataire) ou API directe (paiement sans quitter le site, via un SDK). Point critique : le paiement se confirme côté serveur via des webhooks (le prestataire notifie votre serveur) — pas seulement au retour du client. Rigueur maximale : HTTPS, secrets serveur, idempotence, vérification de signature.
🎯 Objectifs
- Comprendre le modèle d’une passerelle WooCommerce.
- Distinguer paiement par redirection vs API directe.
- Traiter la confirmation via webhooks (et pourquoi c’est indispensable).
- Sécuriser un flux de paiement (secrets, signatures, idempotence).
1. L’anatomie d’une passerelle
Une passerelle étend WC_Payment_Gateway et s’enregistre via le filtre woocommerce_payment_gateways :
add_filter( 'woocommerce_payment_gateways', function ( $gateways ) {
$gateways[] = 'WPR_Gateway';
return $gateways;
} );
add_action( 'plugins_loaded', function () {
if ( ! class_exists( 'WC_Payment_Gateway' ) ) { return; }
class WPR_Gateway extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'wpr_gateway';
$this->method_title = __( 'WPR Pay', 'wpr' );
$this->has_fields = true; // affiche des champs (carte…) au checkout
$this->supports = array( 'products', 'refunds' );
$this->init_form_fields(); // réglages admin
$this->init_settings();
$this->title = $this->get_option( 'title' );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array( 'type' => 'checkbox', 'title' => __( 'Activer', 'wpr' ), 'default' => 'no' ),
'title' => array( 'type' => 'text', 'title' => __( 'Titre', 'wpr' ), 'default' => 'WPR Pay' ),
'api_key' => array( 'type' => 'password', 'title' => __( 'Clé API (secrète)', 'wpr' ) ),
);
}
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
// … créer l'intention de paiement via l'API du prestataire (SDK) …
// Selon le modèle : rediriger, ou confirmer et marquer payé.
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order ), // ou l'URL du prestataire
);
}
}
} );init_form_fields: les réglages (clé API, mode test/prod, titre) — stockés comme options (ch. 5.7).process_payment: le cœur — déclenche le paiement et renvoie un résultat/redirection.supports: capacités (remboursements, abonnements…).
2. Redirection vs API directe
- Redirection : simple, le prestataire gère la page de paiement (moins de contraintes PCI).
- API directe (Stripe Elements…) : paiement sur place, meilleure UX, mais responsabilité accrue (tokenisation côté client, jamais le numéro de carte sur votre serveur).
Dans les deux cas, la confirmation fiable vient du webhook (§3), pas du retour navigateur.
3. Les webhooks : la vérité du paiement
Le retour du client (redirection) peut échouer (fermeture d’onglet, réseau). La source de vérité est le webhook : le prestataire notifie votre serveur de l’événement payment_succeeded / payment_failed.
// Endpoint webhook (route REST custom, ch. 9.3)
add_action( 'rest_api_init', function () {
register_rest_route( 'wpr-pay/v1', '/webhook', array(
'methods' => 'POST',
'callback' => 'wpr_pay_webhook',
'permission_callback' => '__return_true', // public MAIS on vérifie la SIGNATURE
) );
} );
function wpr_pay_webhook( WP_REST_Request $req ) {
$payload = $req->get_body();
$sig = $req->get_header( 'X-Signature' );
if ( ! wpr_pay_verifier_signature( $payload, $sig ) ) { // ⚠️ vérifier la signature
return new WP_Error( 'invalid', 'Signature', array( 'status' => 400 ) );
}
$event = json_decode( $payload, true );
$order = wc_get_order( $event['order_id'] );
if ( $order && ! $order->is_paid() ) { // idempotence
$order->payment_complete( $event['transaction_id'] );
}
return rest_ensure_response( array( 'received' => true ) );
}⚠️ Piège n°1 — se fier au retour navigateur. Marquer une commande « payée » uniquement au retour du client est dangereux : le client peut ne jamais revenir (ou falsifier l’URL de retour). La commande n’est réellement payée que confirmée par le webhook signé du prestataire. C’est la règle absolue des paiements.
⚠️ Piège n°2 — webhook sans vérification de signature. Un endpoint webhook public reçoit des requêtes de n’importe qui : vérifiez la signature (HMAC/secret du prestataire) avant d’agir, sinon un attaquant peut « confirmer » de faux paiements. Et gérez l’idempotence (un même webhook peut arriver plusieurs fois) :
if ( ! $order->is_paid() ).
4. Sécurité des paiements (règles d’or)
- HTTPS partout (obligatoire).
- Clés secrètes côté serveur uniquement (options/
wp-config/env), jamais dans le JS client (seule la clé publiable va côté client, ch. 6.4). - Ne stockez jamais de numéros de carte : tokenisation par le prestataire (PCI-DSS).
- Vérifiez la signature des webhooks ; idempotence des traitements.
- Mode test clairement séparé du mode prod (clés distinctes).
- Journalisez (
wc_get_logger()) les événements de paiement pour le support/litiges.
💡 Pour un dev React — Ce flux est identique à une intégration Stripe classique côté Node/Next : clé publique côté client (Elements), clé secrète côté serveur, webhook signé comme source de vérité, idempotence. WooCommerce fournit juste le cadre (
WC_Payment_Gateway, statuts de commande,payment_complete). Vos réflexes Stripe/paiement s’appliquent directement — un atout, car intégrer/adapter des passerelles est une mission fréquente et bien payée.
5. Souvent : ne pas réinventer
Pour les prestataires majeurs (Stripe, PayPal…), des passerelles officielles/maintenues existent. On développe une passerelle custom surtout pour un prestataire local/de niche ou un besoin spécifique. Dans la plupart des missions, on configure/étend une passerelle existante (hooks) plutôt que d’en écrire une de zéro — plus sûr et plus rapide.
✏️ Exercices
- Quelle classe étend-on pour créer une passerelle, et quelle méthode traite le paiement ?
- Pourquoi ne peut-on pas se contenter du retour navigateur pour marquer une commande payée ?
- Citez deux protections indispensables sur un endpoint webhook de paiement.
✅ Solution
- On étend
WC_Payment_Gateway; la méthodeprocess_payment( $order_id )traite le paiement (et renvoie result/redirect). - Parce que le client peut ne pas revenir (onglet fermé, réseau) ou falsifier l’URL de retour. La confirmation fiable vient du webhook signé du prestataire (source de vérité), qui déclenche
payment_complete. - La vérification de signature (HMAC/secret du prestataire) pour authentifier l’appel, et l’idempotence (ne pas re-traiter un webhook déjà appliqué :
if ( ! $order->is_paid() )). (+ HTTPS.)
🧠 Quiz de révision
1. Qu’est-ce qu’une passerelle de paiement Woo ?
Une classe étendant WC_Payment_Gateway qui déclare ses réglages, s’affiche au checkout et traite le paiement.
2. Où se trouve la « vérité » d’un paiement ?
Dans le webhook signé du prestataire (confirmation serveur), pas dans le retour navigateur.
3. Que vérifie-t-on sur un webhook avant d’agir ?
Sa signature (authenticité), puis on applique l’idempotence (éviter le double traitement).
4. Où vivent les clés secrètes de paiement ?
Côté serveur uniquement ; seule la clé publiable peut aller côté client.
5. Faut-il toujours coder sa passerelle ?
Non : pour les grands prestataires, on utilise/étend une passerelle officielle ; on code custom surtout pour un prestataire de niche.
Chapitre suivant : Store API & WooCommerce headless.