NaabigaPay · Next.js
QuickstartsDocs

DocsQuickstarts › Next.js

Backend

Intégrer NaabigaPay en Next.js

Une route App Router crée le lien avec la clé secrète, un bouton client redirige vers le paiement, et une seconde route vérifie le webhook.

Next.js est à la fois front et serveur. La règle reste la même : la clé secrète (sk_live_…) ne vit que dans vos Route Handlers (côté serveur), jamais dans un composant client. Le bouton appelle votre route, votre route appelle NaabigaPay.

1.La route qui crée le lien

Le composant client appelle cette route ; c'est elle qui appelle POST/v1/payment-links avec la clé secrète, puis renvoie l'url de paiement.

app/api/naabigapay/route.js
import { NextResponse } from 'next/server';

const NPAY = 'https://api.pay.naabiga.com/api/v1';
const SECRET = process.env.NPAY_SECRET; // sk_live_... : reste côté serveur

// Votre bouton client appelle CETTE route, jamais NaabigaPay directement.
export async function POST(req) {
  const { commande, amount } = await req.json();

  const r = await fetch(`${NPAY}/payment-links`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SECRET}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `cmd-${commande}`, // évite un double lien en cas de retry
    },
    body: JSON.stringify({
      amount,                              // entier, en FCFA
      reference: commande,                 // votre numéro de commande
      description: `Commande ${commande}`,
      return_url: 'https://monsite.com/merci',
      cancel_url: 'https://monsite.com/panier',
      single_use: true,
    }),
  });

  if (!r.ok) {
    return NextResponse.json({ error: await r.text() }, { status: 502 });
  }
  const link = await r.json();
  return NextResponse.json({ url: link.url }); // le client ouvre cette URL
}

Pour un montant libre, remplacez amount par amount_min, amount_max et une description.

2.Le bouton côté client

Un composant client ('use client') qui appelle votre route, récupère l'url, puis redirige le navigateur vers la page de paiement.

app/components/BoutonPayer.jsx
'use client';
import { useState } from 'react';

export function BoutonPayer({ commande, montant }) {
  const [chargement, setChargement] = useState(false);

  async function payer() {
    setChargement(true);
    // Appel de VOTRE route serveur (elle seule détient la clé secrète).
    const r = await fetch('/api/naabigapay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ commande, amount: montant }),
    });
    const { url } = await r.json();
    window.location.href = url; // page de paiement NaabigaPay
  }

  return (
    <button onClick={payer} disabled={chargement}>
      {chargement ? 'Redirection…' : 'Payer'}
    </button>
  );
}

3.Vérifier le webhook

NaabigaPay envoie POST à l'URL enregistrée dans le portail marchand, avec les en-têtes X-NPay-Event et X-NPay-Signature (HMAC-SHA256 du corps brut). Forcez le runtime Node (le module crypto n'existe pas sur l'Edge), lisez le corps brut avec req.text(), recalculez le HMAC et comparez en temps constant.

app/api/naabigapay/webhook/route.js
import crypto from 'crypto';

export const runtime = 'nodejs'; // crypto Node requis (pas Edge)

const WEBHOOK_SECRET = process.env.NPAY_WEBHOOK_SECRET;

export async function POST(req) {
  // Corps BRUT, non reparsé : la signature porte sur ces octets exacts.
  const raw = await req.text();
  const signature = req.headers.get('x-npay-signature') || '';

  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(raw, 'utf8')
    .digest('hex');

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response('signature invalide', { status: 400 });
  }

  const { event, data } = JSON.parse(raw);
  if (event === 'payment.succeeded' && req.headers.get('x-npay-event') === 'payment.succeeded') {
    // data.merchant_reference = VOTRE numéro de commande (la reference du lien).
    marquerCommandePayee(data.merchant_reference);
  }
  return new Response('ok', { status: 200 });
}

Astuce. Ne créditez rien depuis le bouton client : le retour navigateur peut être manqué ou rejoué. Seul ce webhook signé, reçu côté serveur, confirme le paiement.

Référence rapide

MéthodeEndpointRôle
POST/v1/payment-linksCréer un lien de paiement
GET/v1/payments/{reference}Statut d'un paiement

Base : https://api.pay.naabiga.com/api/v1 · Auth : Authorization: Bearer sk_…. Voir la documentation complète et le format des webhooks.