Aller au contenu
AfriContext

Wave

Wave - API Checkout

Crée une session de paiement Wave, redirige le client vers wave_launch_url et confirme le paiement par webhook signé.

Vérifié sur la doc officielle • Vérifié sur la doc officielle • 12.10.26API v1

Même prompt, avec et sans ce contexte

Les passages barrés sont ceux que l'IA a inventés. Basculez pour voir la sortie avec le contexte.

› Ajoute un paiement Wave dans ma route Next.js /api/pay.

1export async function POST(req: Request) {2  const { phone, amount } = await req.json();3  const res = await fetch("https://api.wave.com/v1/pay", {4    method: "POST",5    headers: { "X-API-KEY": process.env.WAVE_KEY },6    body: JSON.stringify({7      phone_number: phone,8      amount: amount.toFixed(2),9      currency: "FCFA",10    }),11  });12  const { payment_url } = await res.json();13  return Response.redirect(payment_url);14}
404 Not Found6 inventions de l'IAModèle testé : Claude Sonnet, Cursor (agent)Documentation source

Testez le code généré avant de le brancher sur la vraie API

Une sandbox Wave répond comme l'API et envoie un webhook signé.

Ouvrir la sandbox Wave

Authentification

  • Header Authorization: Bearer <WAVE_API_KEY> sur chaque requête.
  • Une clé API est liée à une seule API (Checkout, Payout, Balance). Une clé Checkout ne sert pas pour Payout.
  • Content-Type: application/json.
  • Toujours lire la clé depuis process.env.WAVE_API_KEY. Jamais de valeur en dur.

Endpoints

  • POST https://api.wave.com/v1/checkout/sessions crée une session.
    • Requis : amount (string, ex. "1000"), currency ("XOF"), success_url, error_url (HTTPS).
    • Optionnel : client_reference (votre identifiant de commande), restrict_payer_mobile (format E.164).
    • Réponse : id (préfixe cos-), wave_launch_url, checkout_status, payment_status, when_expires.
  • GET https://api.wave.com/v1/checkout/sessions/:id lit une session.
  • POST https://api.wave.com/v1/checkout/sessions/:id/expire expire une session ouverte.
  • POST https://api.wave.com/v1/checkout/sessions/:id/refund rembourse une session payée.

Erreurs

  • 400 corps invalide : le JSON contient code et message. Corriger la requête, ne pas réessayer telle quelle.
  • 401 clé absente ou invalide.
  • 403 clé valide mais pas pour cette API.
  • 404 session inconnue.
  • 429 trop de requêtes : réessayer avec backoff exponentiel.
  • 5xx réessayer avec la même Idempotency-Key.

Webhooks

  • Événements : checkout.session.completed, checkout.session.payment_failed.
  • Header Wave-Signature: t=<timestamp>,v1=<signature>.
  • Signature : HMAC-SHA256 de timestamp + corps brut avec WAVE_WEBHOOK_SECRET, comparée en temps constant.
  • Lire le corps brut (await req.text()) avant tout JSON.parse.

Idempotence et limites

  • Envoyer Idempotency-Key: <uuid> sur chaque POST pour éviter les doubles sessions.
  • Une session expire : vérifier when_expires avant de rediriger.
  • Gérer 429 avec backoff, pas de boucle serrée.

Pièges courants

Ce que les LLMs inventent le plus souvent pour Wave.

  • /v1/pay, /v1/payments et /v1/transactions n'existent pas. Le seul point d'entrée est /v1/checkout/sessions.
  • amount est une chaîne sans décimales pour le XOF : "1000", pas 1000.00.
  • La devise est XOF, jamais FCFA ni CFA.
  • Pas de header X-API-KEY : c'est Authorization: Bearer.
  • Ne pas inventer de domaine sandbox.wave.com : tester contre la sandbox AfriContext.
  • Le paiement n'est confirmé que par le webhook, pas par l'arrivée sur success_url.

Exemple minimal

1type WaveSession = {2  id: string;3  wave_launch_url: string;4  checkout_status: "open" | "complete" | "expired";5  payment_status: "processing" | "cancelled" | "succeeded";6};7 8export async function createWaveCheckout(orderId: string, amountXof: number): Promise<WaveSession> {9  const res = await fetch("https://api.wave.com/v1/checkout/sessions", {10    method: "POST",11    headers: {12      Authorization: "Bearer " + process.env.WAVE_API_KEY,13      "Content-Type": "application/json",14      "Idempotency-Key": crypto.randomUUID(),15    },16    body: JSON.stringify({17      amount: String(Math.round(amountXof)),18      currency: "XOF",19      client_reference: orderId,20      success_url: process.env.APP_URL + "/paiement/succes",21      error_url: process.env.APP_URL + "/paiement/erreur",22    }),23  });24  if (!res.ok) throw new Error("Wave " + res.status + ": " + (await res.text()));25  return res.json();26}

Variables d'environnement

  • WAVE_API_KEY
  • WAVE_WEBHOOK_SECRET
  • APP_URL