WordPress et WooCommerce
Un plugin, neuf intégrations et les hooks nécessaires pour créer la vôtre.
Le plugin Wajub transforme un site WordPress en surface de paiement. Il détecte les plugins d'e-commerce, de dons, de LMS, d'adhésion et de formulaires déjà installés. Il s'enregistre comme moyen de paiement dans chacun d'eux et utilise des shortcodes lorsqu'aucune passerelle native n'existe. Un jeu de clés et un webhook suffisent pour toutes les intégrations.
Fonctionnement du plugin
Neuf intégrations fournissent une véritable passerelle. Le plugin ne charge chacune d'elles que s'il détecte le plugin correspondant. Une installation avec seulement WooCommerce ne charge donc jamais le code de GiveWP ou des LMS.
| Intégration | Détectée par | Checkout | Remboursements depuis WordPress |
|---|---|---|---|
| WooCommerce | Classe WooCommerce | Redirection, en ligne, superposition, Blocks | Oui |
| Easy Digital Downloads | EDD_VERSION | Redirection | Oui, au changement de statut |
| GiveWP 3.x | givewp_register_payment_gateway | Redirection | Oui |
| GiveWP avant 2.18 | GIVE_VERSION | Redirection | Oui, au changement de statut |
| Charitable | CHARITABLE_VERSION | Redirection | Oui |
| Tutor LMS | TUTOR_VERSION | Checkout Ecommerce natif | Oui |
| LifterLMS | LLMS_PLUGIN_FILE | Redirection | Oui |
| LearnDash | LEARNDASH_VERSION | Bouton shortcode | Aucun modèle de commande à rembourser |
| MemberPress | MEPR_VERSION | Redirection | Oui |
| Gravity Forms | Classe GFForms | Redirection | Oui |
Cinq autres apparaissent dans l'administration avec un badge shortcode. Ils ne proposent aucune
API de passerelle à laquelle le plugin peut se connecter. Ajoutez plutôt [wajub_pay] sur la page :
WPForms, Bookly, The Events Calendar, WP Simple Pay et Amelia. WP Crowdfunding passe par
WooCommerce. L'activation de l'intégration WooCommerce le prend donc en charge.
Les abonnements ne sont pris en charge nulle part
WooCommerce Subscriptions, la facturation récurrente de MemberPress et les dons récurrents de
GiveWP exigent tous un moyen de paiement enregistré que la passerelle peut débiter de nouveau.
Ce plugin n'en possède aucun. La version 1.2.0 a donc retiré l'option subscriptions de la
passerelle WooCommerce. Wajub n'est pas proposé au checkout pour un produit avec abonnement,
au lieu d'échouer silencieusement lors du premier renouvellement.
Prérequis
| Composant | Minimum | Remarque |
|---|---|---|
| WordPress | 6.2 | Testé jusqu'à la version 7.0 |
| PHP | 8.1 | En dessous, le plugin affiche une notification et s'arrête |
| WooCommerce | 8.0 | Seulement si vous l'utilisez. Testé jusqu'à la version 11.0 |
| HTTPS | Obligatoire | Le webhook et l'URL de retour en ont besoin |
| Un compte Wajub | Obligatoire | Les clés de sandbox suffisent pour commencer |
Le plugin se déclare compatible avec les deux fonctionnalités WooCommerce qui rendent les anciennes passerelles incompatibles : High Performance Order Storage ainsi que le panier et le checkout Blocks. Vous n'avez donc pas besoin de conserver les anciennes tables de commandes.
Installer le plugin
Le plugin est distribué sous forme d'archive zip, pas dans le répertoire WordPress.org. Il n'est donc pas disponible dans la recherche et ne se met pas à jour automatiquement. Demandez la version actuelle à votre contact Wajub.
- 1
Téléverser l'archive zip
Dans l'administration WordPress, accédez à Plugins, Add New Plugin, Upload Plugin, choisissez l'archive zip et installez-la. Activez le plugin à la fin du téléversement.
- 2
Ou l'installer depuis la ligne de commande
WP-CLI accepte directement le chemin ou l'URL de l'archive zip.
- 3
Ou déposer le dossier par SFTP
Décompressez l'archive localement et téléversez le dossier dans
wp-content/plugins/, puis activez-le depuis l'écran des plugins.
WP-CLI effectue la même installation en une ligne. La seconde commande confirme que le plugin est actif sous le bon nom de dossier.
wp plugin install ./wajub.zip --activate
wp plugin list --name=wajub --fields=name,status,versionLe dossier doit s'appeler wajub
L'extension Gravity Forms s'identifie par le chemin wajub/wajub.php, et le plugin construit
les URL de ses ressources depuis le même dossier. Une archive extraite dans wajub-wordpress
ou wajub-main casse le feed Gravity Forms et rend le CSS front-end inaccessible. Renommez le
dossier en wajub avant l'activation.
L'activation effectue trois opérations. Elle crée les pages Payment Successful, Payment Failed
et Payment Processing, chacune avec un shortcode [wajub_pay_message]. Elle crée la table
wp_wajub_transactions. Enfin, elle programme l'actualisation des règles de réécriture pour que
les routes REST répondent immédiatement.
Ces trois pages sont des pages ordinaires. Modifiez-les, personnalisez leur style et adaptez-les à
votre thème. Le plugin a seulement besoin de leurs identifiants, stockés dans
wajub_success_page_id, wajub_failure_page_id et wajub_callback_page_id.
Connecter votre compte Wajub
Accédez à Wajub, Settings. Le plugin conserve deux jeux d'identifiants, sandbox et live. Une option détermine celui que chaque requête utilise.
| Option | Contenu | Utilisée lorsque |
|---|---|---|
wajub_test_mode | Option de sandbox, activée par défaut | Toujours, elle choisit la paire ci-dessous |
wajub_secret_key_test | Clé sk_test. | Le mode test est activé |
wajub_webhook_secret_test | Secret whsec_test_ | Le mode test est activé |
wajub_secret_key | Clé sk. | Le mode test est désactivé |
wajub_webhook_secret | Secret whsec_ | Le mode test est désactivé |
wajub_debug_log | Option des logs de débogage | Écrit dans les logs WooCommerce ou dans error_log |
Une clé secrète Wajub contient un préfixe, un point, puis 96 caractères. Les clés live commencent
par sk., et celles de la sandbox par sk_test.. Copiez-les depuis
le Dashboard. Le plugin ne demande jamais
de clé publique : le navigateur reçoit un jeton de session créé par votre serveur, jamais une clé.
Un champ sandbox vide utilise la clé live en repli
Le client API lit la clé de sandbox en mode test. Si ce champ est vide, il utilise la clé live au lieu d'échouer. Un site qui semble utiliser la sandbox débite alors de l'argent réel. Remplissez les deux champs, ou laissez le champ live vide jusqu'à ce que vous soyez prêt à passer en live.
Aucun élément ne lit automatiquement une variable d'environnement, mais WordPress fournit le hook
nécessaire. Le filtre natif pre_option_ court-circuite toute lecture d'option. Vous pouvez ainsi
conserver entièrement les secrets hors de la base de données.
<?php
// wp-content/mu-plugins/wajub-keys.php
foreach ([
'wajub_secret_key' => 'WAJUB_SECRET_KEY',
'wajub_secret_key_test' => 'WAJUB_SECRET_KEY_TEST',
'wajub_webhook_secret' => 'WAJUB_WEBHOOK_SECRET',
'wajub_webhook_secret_test' => 'WAJUB_WEBHOOK_SECRET_TEST',
] as $option => $env) {
add_filter("pre_option_{$option}", static function () use ($env) {
$value = getenv($env);
return $value === false || $value === '' ? false : $value;
});
}Renvoyer false permet à WordPress de lire normalement l'option enregistrée. Les champs de
l'administration fonctionnent donc toujours sur une machine où les variables ne sont pas définies.
Le tableau de bord sous Wajub appelle GET /payments à chaque chargement de page pour vérifier
les clés. Connected indique une clé valide, Error un refus de l'API et Not configured un champ vide.
Enregistrer le webhook
Le webhook est obligatoire. Lui seul confirme un paiement lorsque le client ferme l'onglet, perd le réseau après avoir approuvé sur son téléphone ou utilise un canal dont le règlement prend plusieurs minutes. Toutes les intégrations de ce plugin en dépendent.
Copiez l'URL depuis Wajub, Settings, ou construisez-la vous-même.
| Élément | Valeur |
|---|---|
| URL | https://your-site.com/wp-json/wajub/v1/webhook |
| Method | POST |
| En-tête de signature | X-Wajub-Signature: v1=<hmac> |
| En-tête d'horodatage | X-Wajub-Timestamp |
| Payload signé | L'horodatage, un point, puis le corps brut |
| Algorithme | HMAC SHA-256 avec votre secret de webhook |
| Tolérance | 300 secondes |
Collez-la dans les endpoints de webhook du Dashboard Wajub. Copiez ensuite le secret de signature fourni dans le champ correspondant sous Wajub, Settings, puis enregistrez.
Sans secret, chaque livraison est rejetée
Le handler renvoie 403 sans rien faire lorsque le champ du secret de webhook de l'environnement
actuel est vide. Aucun mode non signé n'existe. Une commande bloquée sur pending après un
paiement réussi vient presque toujours de ce problème.
Vous pouvez vérifier le fonctionnement de l'endpoint depuis votre terminal. Signez un payload comme Wajub, puis envoyez-le.
SECRET='whsec_test_your_secret_here'
BODY='{"id":"evt_local_test","event":"payment.succeeded","data":{"reference":"wc_42_1757000000","status":"succeeded","amount":25000,"currency":"XAF","metadata":{"source":"woocommerce","source_id":"42"}}}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d" " -f1)
curl -i -X POST https://your-site.com/wp-json/wajub/v1/webhook \
-H "Content-Type: application/json" \
-H "X-Wajub-Timestamp: $TS" \
-H "X-Wajub-Signature: v1=$SIG" \
-d "$BODY"Une signature valide renvoie {"received":true}. Une signature incorrecte renvoie 403. Rejouer
le même id renvoie {"received":true} sans répéter le traitement, car le handler conserve
pendant une semaine chaque identifiant d'événement reçu.
Deux détails sont importants. Le handler accepte type et event comme clé du nom d'événement,
ce qui compte car le format Wajub utilise event. Il route aussi le traitement avec
metadata.source et metadata.source_id, les deux champs écrits par chaque intégration lors de
la création du paiement. Cette paire indique au webhook la commande, le don ou l'entrée à finaliser.
WooCommerce
WooCommerce bénéficie de l'intégration la plus complète : trois modes de checkout, la prise en charge de Blocks, les remboursements dans les deux sens, les notifications de litige sur la commande et un parcours de nouvelle tentative qui reconstruit le panier.
Activer la passerelle
Accédez à WooCommerce, Settings, Payments et activez Wajub. La passerelle reste masquée dans le checkout tant qu'aucune clé secrète n'est enregistrée. Si elle n'apparaît pas, les clés sont absentes.
Choisir un mode de checkout
| Mode | Appel du SDK | Lieu du paiement |
|---|---|---|
redirect | authorization_url | Sur le checkout hébergé Wajub, puis retour vers votre page de remerciement |
inline | wajub.mount() | Sur votre page de checkout, dans un cadre intégré |
overlay | wajub.open() | Sur votre page de checkout, dans une fenêtre modale |
La redirection est le mode par défaut et le meilleur point de départ. Elle comporte le moins d'éléments et reste le seul mode fonctionnel lorsque JavaScript est désactivé.
Les modes en ligne et en superposition chargent Wajub.js depuis
https://js.wajub.com et gardent le client sur la page de checkout. Deux réglages supplémentaires
s'appliquent : une locale et un thème system, light ou dark.
Les libellés des modes dans les réglages sont obsolètes
La liste indique encore que les modes en ligne et en superposition s'intègrent à la page de paiement de la commande. Depuis la version 1.2.0, ils s'intègrent directement à la page de checkout, sans navigation. Le texte d'aide sous le champ est exact.
Maintien des modes en ligne et en superposition sur la page
Le checkout classique et le checkout Blocks suivent des parcours différents pour obtenir le même résultat. Tous deux utilisent une page fonctionnelle en repli si JavaScript ne s'exécute pas.
Dans le checkout classique, WooCommerce déclenche checkout_place_order_success sur le formulaire
avec le résultat AJAX, juste avant de lire result.redirect et de naviguer. Le plugin l'écoute,
remplace cette redirection par un fragment du même document et monte le widget de paiement sur place.
Dans le checkout Blocks, WooCommerce attend les observateurs onCheckoutSuccess avant de choisir
la destination. Le plugin renvoie donc une promesse qui reste en attente pendant le paiement et se
résout avec la destination réelle.
Si l'un des scripts ne se charge pas, WooCommerce suit la redirection originale vers la page de paiement de la commande, où le même widget est rendu côté serveur. Le client peut toujours payer, avec une navigation supplémentaire.
Tant que le widget est ouvert, la page interroge wajub/v1/embed-status toutes les trois secondes
pendant dix minutes au maximum. Cet endpoint demande le statut réel du paiement à l'API, finalise
la commande en cas de réussite et renvoie une URL de nouvelle tentative en cas d'échec.
Remboursements
Les remboursements fonctionnent dans les deux sens.
Un remboursement lancé dans WooCommerce appelle POST /refunds et inscrit l'identifiant du
remboursement Wajub sur la commande pour éviter un double enregistrement par le webhook. Un
remboursement lancé dans le Dashboard Wajub arrive par le webhook refund.succeeded et crée le
remboursement WooCommerce correspondant avec refund_payment défini sur false, car l'argent a déjà été déplacé.
La métadonnée de commande _wajub_refund_<id> sécurise les remboursements partiels. Un webhook
dont l'identifiant de remboursement est déjà marqué est ignoré. Un montant supérieur au solde
remboursable est rejeté au lieu d'être plafonné.
Métadonnées de commande écrites par le plugin
| Clé de métadonnée | Contenu |
|---|---|
_wajub_reference | Votre référence, wc_<order id>_<timestamp> |
_wajub_payment_id | L'identifiant du paiement Wajub, trx_… |
_wajub_session_id | Le jeton d'autorisation utilisé par les modes en ligne et en superposition |
_wajub_mode | Le mode utilisé pour créer la commande |
_wajub_dispute_status | Le dernier événement de litige reçu pour cette commande |
_wajub_refund_<id> | Indique qu'un remboursement Wajub a déjà été enregistré |
Lisez-les avec $order->get_meta() dans votre code. Consignez la référence dans les logs, car
elle apparaît dans le Dashboard Wajub et dans chaque webhook.
Litiges
Un événement dispute.created, dispute.won ou dispute.lost retrouve la commande par référence
ou identifiant de paiement, écrit _wajub_dispute_status, ajoute une note avec le motif et le
montant, puis envoie au responsable du site un e-mail avec l'échéance de réponse. Les événements
intermédiaires sont enregistrés sur la commande sans e-mail.
Nouvelle tentative après un échec
Lorsqu'un paiement échoue ou que la confirmation expire, le client reçoit un lien de nouvelle tentative. Ce lien annule la commande pending, remet les mêmes produits et variations dans le panier, puis renvoie le client au checkout avec une notification. Il contient un nonce et ne peut donc être ni partagé ni rejoué.
Dons avec GiveWP
Les deux générations de GiveWP sont prises en charge. Le plugin choisit la bonne au chargement.
GiveWP 3 et les versions ultérieures s'enregistrent avec givewp_register_payment_gateway sous
l'identifiant wajub-givewp. Activez-le dans Donations, Settings, Payment Gateways.
Les donateurs sont redirigés vers le checkout hébergé, puis reviennent par une route sécurisée qui
vérifie le don, définit son statut et les envoie vers votre page de réussite.
GiveWP avant la version 2.18 utilise l'ancien filtre give_payment_gateways et un callback
give-listener. La configuration reste identique.
Wajub statuses map onto Give statuses rather than collapsing into a single failure:
| Statut Wajub | Statut du don GiveWP |
|---|---|
succeeded | Complete |
canceled, cancelled, expired | Cancelled |
abandoned | Abandoned |
failed, rejected | Failed |
refunded, partially-refunded | Refunded |
Rembourser un don depuis l'administration GiveWP appelle l'API Wajub. Les dons récurrents ne sont pas pris en charge.
GiveWP utilise uniquement la redirection
Le formulaire de don redirige vers le checkout hébergé. Pour une expérience en ligne ou en
superposition, construisez plutôt la page avec le shortcode [wajub_pay type="donation"], qui
prend en charge les trois modes.
Dons avec Charitable
La passerelle étend Charitable_Gateway et s'enregistre automatiquement. Elle lit les clés dans
Wajub, Settings. L'écran des réglages de la passerelle Charitable ne contient donc rien à remplir.
Les donateurs sont redirigés vers le checkout hébergé. À leur retour, le plugin vérifie le paiement
auprès de l'API, puis confirme que la référence enregistrée et source_id correspondent au don.
Il marque ensuite le don comme terminé et affiche le reçu. Un remboursement depuis l'administration
Charitable appelle POST /refunds une seule fois et marque le don pour ignorer un second appel.
Easy Digital Downloads
Wajub apparaît dans le checkout EDD sous Wajub (Mobile Money, Card). Aucun formulaire de carte n'est à afficher, la passerelle le masque donc.
Le flux crée d'abord un paiement EDD pending, puis le paiement Wajub, avant de rediriger. Au retour, le plugin vérifie le paiement auprès de l'API. Il le publie avec une note contenant la référence, ou le marque comme échoué avec son motif.
EDD pilote lui-même les remboursements. Passer un paiement à refunded ou partially_refunded
appelle l'API Wajub une seule fois. La métadonnée _wajub_refund_api_done bloque un second appel.
Cours avec Tutor LMS, LifterLMS et LearnDash
Les trois plugins LMS proposent trois niveaux d'intégration différents, car leurs surfaces d'extension ne sont pas les mêmes.
Tutor LMS bénéficie d'une passerelle native. Elle s'intègre à Tutor Ecommerce avec
tutor_payment_gateways. Wajub apparaît dans le checkout Tutor normal et l'inscription suit le
flux de commande de Tutor. Les remboursements lancés dans Tutor appellent l'API. Si Tutor
Ecommerce est désactivé, un bouton [wajub_pay] apparaît sur la page du cours.
LifterLMS bénéficie aussi d'une véritable passerelle. Elle étend LLMS_Payment_Gateway et
s'enregistre avec lifterlms_payment_gateways. Une commande payante redirige vers le checkout
hébergé. Le webhook de confirmation enregistre la transaction sur la commande LifterLMS et inscrit l'étudiant.
LearnDash ne propose aucun modèle de commande exploitable par ce plugin. Un bouton de paiement
est donc ajouté aux boutons du cours, et l'accès est accordé avec le hook wajub_payment_complete.
Aucun remboursement n'est possible depuis WordPress. Utilisez plutôt le Dashboard Wajub.
Les boutons LMS disparaissent lorsque WooCommerce est actif
Le bouton LearnDash et l'option du tableau tarifaire LifterLMS recherchent d'abord WooCommerce et n'affichent rien s'il est installé. Ce comportement convient à un site qui vend ses cours comme produits WooCommerce, car la passerelle les prend déjà en charge. Si WooCommerce sert à autre chose, le bouton n'apparaît pas. Utilisez alors le shortcode.
Adhésions avec MemberPress
La passerelle s'enregistre avec mepr-gateway-paths et gère les paiements uniques d'adhésion. Un
achat redirige vers le checkout hébergé. Le callback de retour vérifie ensuite le paiement,
finalise la transaction MemberPress et envoie le reçu.
La facturation récurrente n'est pas implémentée. Une adhésion avec un tarif d'abonnement ne sera pas débitée automatiquement une nouvelle fois.
Gravity Forms
L'extension repose sur le framework d'extensions de paiement de Gravity Forms. Elle se comporte donc comme vos autres feeds de paiement : créez un feed sur le formulaire, associez le montant et l'e-mail, puis Wajub s'occupe du reste.
Les soumissions redirigent vers le checkout hébergé. Le callback de retour vérifie le paiement
auprès de l'API, compare la référence enregistrée et source_id à l'entrée, marque celle-ci comme
Paid, puis déclenche gform_post_payment_status pour exécuter vos feeds et notifications.
Un remboursement depuis l'entrée Gravity Forms appelle l'API Wajub une seule fois, grâce à la
métadonnée d'entrée wajub_refund_api_done.
Le feed propose un type d'abonnement sans effet
La liste du type de transaction affiche Subscription avec Products and Services, car il s'agit du champ standard de Gravity Forms. Wajub ne gère aucun renouvellement. Un feed d'abonnement encaisse le premier paiement, puis ne débite plus rien. Choisissez Products and Services.
Tout autre plugin avec des shortcodes
Trois shortcodes couvrent tous les plugins sans passerelle et toutes vos pages personnalisées. Ils utilisent une route REST publique qui crée le paiement côté serveur. Aucune clé n'atteint donc le navigateur.
Un bouton de paiement
[wajub_pay] affiche un petit formulaire qui demande un e-mail et, facultativement, un nom. Il
lance ensuite le paiement dans le mode choisi.
amountnumberfacultatifcurrencystringfacultatifdéfaut : XAFmoderedirect | inline | overlayfacultatifdéfaut : redirecttypepayment | donationfacultatifdéfaut : paymentdescriptionstringfacultatifproduct_idnumberfacultatifcontent_idstringfacultatifbutton_textstringfacultatifdéfaut : Pay with Wajubclassstringfacultatif[wajub_pay amount="25000" currency="XAF" description="Consultation" mode="redirect"]
[wajub_pay product_id="482" mode="overlay" button_text="Buy now"]
[wajub_pay type="donation" currency="XAF" mode="inline" button_text="Support us"]Contenu verrouillé
[wajub_pay_content] enveloppe un contenu qui apparaît seulement après le paiement du lecteur.
En attendant, il affiche le même formulaire et utilise par défaut le mode en superposition afin
que la page ne change pas sous ses yeux.
[wajub_pay_content content_id="masterclass-2026" amount="15000" currency="XAF"]
The full recording, the slides, and the worksheet live here.
[/wajub_pay_content]L'accès est mémorisé de deux façons : sur le compte lorsque l'acheteur est connecté, ou pendant trente jours dans une donnée temporaire indexée par l'identifiant du contenu et son e-mail lorsqu'il ne l'est pas. Un acheteur déconnecté qui change de navigateur devra payer de nouveau. Cet outil convient donc à un téléchargement numérique ou à un article unique, pas à une adhésion. MemberPress ou LifterLMS gèrent correctement ce dernier cas.
Message de résultat
[wajub_pay_message] affiche le message de réussite, d'échec ou de traitement. Les trois pages
créées à l'activation le contiennent. Il n'affiche rien si l'URL ne contient pas de paramètre
reference, et reste donc invisible lors d'une visite directe.
Shortcodes dans un template de thème
En dehors de l'éditeur, exécutez-les avec do_shortcode().
<?php
$amount = (float) get_post_meta(get_the_ID(), 'ticket_price', true);
if ($amount > 0) {
echo do_shortcode(sprintf(
'[wajub_pay amount="%s" currency="XAF" description="%s" mode="overlay"]',
esc_attr((string) $amount),
esc_attr(get_the_title())
));
}Le cache de page complète casse le formulaire du shortcode
Le formulaire contient un nonce WordPress, valide pendant 24 heures au maximum. Une page mise
en cache plus longtemps fournit un nonce expiré. La route REST renvoie alors 403 et demande au
visiteur d'actualiser. Excluez du cache les pages contenant [wajub_pay], ou conservez une durée
de cache inférieure à un jour.
Créer votre propre intégration
Tout ce qui précède repose sur trois éléments que vous pouvez utiliser directement : un client API, un jeton de callback HMAC et un ensemble de hooks déclenchés par le handler de webhook.
Hooks
| Hook | Arguments | Déclenché lorsque |
|---|---|---|
wajub_payment_complete | $reference, $data, $source, $sourceId | Un paiement réussit |
wajub_payment_failed | $reference, $data, $source, $sourceId | Un paiement échoue, est annulé, abandonné, rejeté ou expiré |
wajub_refund_complete | $paymentRef, $data, 'woocommerce', $orderId | Un remboursement réussit sur une commande WooCommerce |
wajub_dispute_received | $eventType, $data | Tout événement de litige |
wajub_webhook_received | $eventType, $data | Chaque événement vérifié, après le routage |
wajub_webhook_missing_secret | $rawPayload | Un événement arrive sans secret configuré |
wajub_webhook_invalid_signature | $rawPayload | Un événement échoue à la vérification de signature ou d'horodatage |
wajub_default_phone_country_code | '237' | Un numéro à neuf chiffres a besoin d'un indicatif pays |
Le dernier est un filtre. Les autres sont des actions. $source et $sourceId proviennent
directement des metadata définies à la création du paiement. Votre propre nom de source peut
ainsi fonctionner.
Livrer votre propre enregistrement
Définissez votre propre metadata.source lors de la création du paiement, puis traitez-la. Le hook
s'exécute dans le webhook, seul endroit où le paiement est confirmé et non supposé réussi.
<?php
add_action('wajub_payment_complete', static function (
string $reference,
array $data,
string $source,
$sourceId
): void {
if ($source !== 'bookings' || empty($sourceId)) {
return;
}
$booking = get_post((int) $sourceId);
if (! $booking || $booking->post_type !== 'booking') {
return;
}
update_post_meta($booking->ID, 'payment_status', 'paid');
update_post_meta($booking->ID, 'wajub_reference', $reference);
update_post_meta($booking->ID, 'paid_amount', (float) ($data['amount'] ?? 0));
do_action('bookings_confirmed', $booking->ID);
}, 10, 4);Créer un paiement depuis votre code
Client::getInstance() fournit un client configuré avec le jeu de clés choisi par l'option du
mode test. Les montants utilisent l'unité principale. Le client les arrondit automatiquement pour
les devises sans décimales.
<?php
use WajubPay\API\Client;
use WajubPay\Security\CallbackToken;
function bookings_start_payment(int $bookingId, float $amount, string $email): string
{
$callback = add_query_arg([
'bookings' => 'wajub_return',
'booking_id' => $bookingId,
'_wajub_token' => CallbackToken::generate($bookingId),
], home_url('/'));
$result = Client::getInstance()->createPayment([
'amount' => $amount,
'currency' => 'XAF',
'description' => sprintf('Booking #%d', $bookingId),
'reference' => 'booking_' . $bookingId . '_' . time(),
'callback' => $callback,
'customer' => ['email' => $email],
'metadata' => [
'source' => 'bookings',
'source_id' => (string) $bookingId,
],
]);
$parsed = Client::parsePaymentResponse($result);
update_post_meta($bookingId, 'wajub_reference', $parsed['reference']);
return $parsed['authorization_url'];
}parsePaymentResponse() aplatit l'enveloppe de l'API en id, reference, session_id,
authorization_url et status. Envoyez le client vers authorization_url pour un checkout avec
redirection, ou passez session_id à wajub.mount() pour une intégration en ligne.
L'URL de callback est la destination du client à son retour. Il s'agit d'une redirection du navigateur. Utilisez-la seulement pour choisir la page à afficher, jamais comme preuve de paiement. Vérifiez le jeton, puis le paiement.
<?php
use WajubPay\API\Client;
use WajubPay\Security\CallbackToken;
add_action('init', static function (): void {
$action = isset($_GET['bookings']) ? sanitize_text_field(wp_unslash($_GET['bookings'])) : '';
if ($action !== 'wajub_return') {
return;
}
$bookingId = absint($_GET['booking_id'] ?? 0);
$token = sanitize_text_field(wp_unslash($_GET['_wajub_token'] ?? ''));
if (! $bookingId || ! CallbackToken::verify($bookingId, $token)) {
wp_safe_redirect(home_url('/'));
exit;
}
$reference = (string) get_post_meta($bookingId, 'wajub_reference', true);
$status = 'pending';
try {
$payment = Client::getInstance()->retrievePayment($reference);
$status = (string) (Client::extractTransaction($payment)['status'] ?? 'pending');
} catch (\Throwable) {
// The webhook is still the source of truth. Show a waiting page.
}
wp_safe_redirect(Client::isSucceededStatus($status)
? home_url('/booking-confirmed/')
: home_url('/booking-pending/'));
exit;
});CallbackToken::generate() produit un HMAC de l'identifiant, éventuellement salé avec une seconde
valeur comme une clé de commande, puis signé avec votre secret de webhook. Il empêche un visiteur
de confirmer la réservation d'une autre personne en modifiant l'identifiant dans l'URL. Passez
toujours à verify() le même second argument qu'à generate().
Modifier l'indicatif pays par défaut
Un numéro de téléphone à neuf chiffres sans indicatif est considéré comme camerounais. Modifiez ce réglage une fois pour tout le site.
<?php
add_filter('wajub_default_phone_country_code', static fn (): string => '221');Fonctionnalités du client API
| Méthode | Rôle |
|---|---|
createPayment(array $params) | POST /payments, renvoie l'enveloppe brute |
retrievePayment(string $reference) | GET /payments/{reference} |
cancelPayment(string $reference) | DELETE /payments/{reference} |
createRefund(string $paymentId, ?float $amount, ?string $reason, string $currency) | POST /refunds |
refundByStoredIds(string $paymentId, string $reference, …) | Résout d'abord l'identifiant trx_, puis rembourse |
testConnection() | Un simple appel GET /payments, renvoie un booléen |
isConfigured(), isTestMode() | Lit les réglages enregistrés |
Chaque requête transmet la clé secrète brute dans Authorization, sans préfixe bearer, avec un
délai d'expiration de 30 secondes et une Idempotency-Key pour les écritures. Elle est retentée
deux fois après une erreur réseau, une erreur 429 ou une erreur 5xx. Les échecs déclenchent une
exception typée : AuthenticationException, PermissionException, NotFoundException,
RateLimitException, InvalidRequestException ou ApiConnectionException. Toutes étendent
ApiException avec les propriétés en lecture seule errorCode, httpStatus et body.
Routes REST
Quatre routes se trouvent sous wajub/v1. Elles sont toutes publiques par conception et chacune
porte sa propre preuve.
| Route | Méthode | Protégée par |
|---|---|---|
/webhook | POST | Une signature HMAC et une fenêtre d'horodatage de 300 secondes |
/callback | GET | Rien, elle redirige seulement. Le statut est relu depuis l'API |
/embed-status | GET | Un nonce lié à l'identifiant de commande, avec la clé de commande |
/create-payment | POST | Un nonce WordPress, avec 20 requêtes par minute et par IP |
Un ancien chemin de webhook, POST /?wajub=webhook, est conservé pour les sites configurés avant
la création de la route REST. Il vérifie la même signature. Utilisez la route REST pour toute
nouvelle intégration.
Table des transactions
Chaque webhook écrit une ligne dans wp_wajub_transactions, quelle que soit son intégration.
C'est le seul endroit où une commande WooCommerce, un don GiveWP et un paiement par shortcode
apparaissent ensemble. Wajub, Transactions affiche cette table.
| Colonne | Contenu |
|---|---|
reference | Votre référence unique |
trxref | La référence du côté de Wajub |
amount, currency | Valeurs envoyées par l'API |
status | Le dernier statut reçu |
source, source_id | La paire de métadonnées qui a routé l'événement |
customer_email | Provient du paiement |
environment | test ou live, selon l'option au moment de l'écriture |
metadata, payload | JSON |
created_at, updated_at | Horodatages |
Le tableau de bord totalise le volume réussi par devise au lieu de tout additionner. Un site qui accepte le XAF et le GHS affiche donc deux chiffres plutôt qu'un total sans signification.
Désinstaller le plugin supprime cette table et toutes les options wajub_. Le désactiver ne les
supprime pas. Exportez les données nécessaires avant de supprimer le plugin.
Passer en live
- 1
Tester tout le parcours dans la sandbox
Passez une vraie commande avec un numéro Mobile Money de la sandbox et vérifiez que la commande atteint un état payé sans intervention dans l'administration. Testez aussi une annulation et un remboursement.
- 2
Confirmer le déclenchement du webhook
La commande doit se terminer même si vous fermez l'onglet juste après l'approbation. Si elle se termine seulement à votre retour sur le site, le webhook n'est pas configuré et vous dépendez du callback.
- 3
Ajouter les identifiants live
Collez la clé secrète live et le secret de webhook live, puis enregistrez la même URL comme endpoint de webhook live dans le Dashboard. Les endpoints live et sandbox sont distincts.
- 4
Désactiver le mode test
Décochez le mode test dans Wajub, Settings. Vérifiez que le tableau de bord affiche Live et Connected.
- 5
Encaisser un paiement réel
Encaissez un petit montant sur un vrai téléphone, puis remboursez-le. C'est le seul test qui vérifie ensemble les clés live, le webhook live et votre processus de livraison.
L'activation du compte et le KYC qui déverrouille les clés live sont présentés dans Passer en live.
Dépannage
| Symptôme | Cause | Solution |
|---|---|---|
| Wajub est absent du checkout WooCommerce | Aucune clé secrète enregistrée pour l'environnement actuel | La passerelle reste masquée sans clé. Remplissez le champ de l'environnement choisi par l'option |
| Les commandes restent sur pending après un paiement réussi | Le champ du secret de webhook est vide ou l'endpoint n'est pas enregistré | Chaque livraison est refusée avec 403 tant que le secret ne correspond pas. Envoyez la requête de test signée ci-dessus |
| De l'argent réel a été déplacé en mode test | Le champ de clé sandbox était vide et le client a utilisé la clé live | Remplissez les deux champs ou effacez la clé live jusqu'au passage en live |
| Le formulaire du shortcode renvoie une erreur de requête invalide | Un nonce expiré servi par un cache de page complète | Excluez la page du cache ou réduisez sa durée à moins de 24 heures |
| Le feed Gravity Forms n'apparaît pas | Le dossier du plugin ne s'appelle pas wajub | Renommez-le en wajub et réactivez le plugin |
| Le checkout en ligne redirige vers une page de paiement séparée | Le script d'intégration ne s'est pas chargé | Recherchez dans la console une requête bloquée vers js.wajub.com. La redirection est le repli prévu |
| Un montant est cent fois trop élevé | Utilisation habituelle des unités mineures | Wajub utilise l'unité principale. Envoyez 25 000 pour vingt-cinq mille francs, pas 2 500 000 |
| Le tableau de bord d'administration est lent | Il appelle l'API à chaque chargement pour afficher l'état de la connexion | Comportement attendu. Il s'agit d'une seule requête, uniquement sur les écrans d'administration Wajub |
| Rien n'apparaît dans les logs | Les logs de débogage sont désactivés | Activez-les dans les réglages ou définissez WP_DEBUG. Lorsque WooCommerce est actif, la sortie va dans ses logs sous la source wajub. Sinon, elle va dans error_log |
Pour tout autre problème, activez les logs de débogage, reproduisez-le, puis envoyez les logs au support avec la référence de transaction. N'envoyez jamais de clé.