Docs › Quickstarts › Java / Spring
BackendIntégrer NaabigaPay en Java / Spring
Un contrôleur crée le lien de paiement avec HttpClient, un endpoint webhook vérifie la signature avec Mac HmacSHA256, puis marque la commande payée.
Deux méthodes côté serveur : l'une crée un lien de paiement quand un client veut payer, l'autre vérifie la signature du webhook une fois le paiement réussi. La clé secrète (sk_live_…) reste dans votre configuration serveur et n'atteint jamais le navigateur.
1.Créer le lien
Votre front appelle cette route ; elle appelle POST/v1/payment-links avec la clé secrète, puis renvoie l'url de paiement.
package com.monsite.pay;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaiementController {
private static final String NPAY = "https://api.pay.naabiga.com/api/v1";
private static final String SECRET = System.getenv("NPAY_SECRET"); // sk_live_...
private final HttpClient http = HttpClient.newHttpClient();
private final ObjectMapper json = new ObjectMapper();
// Votre front appelle CETTE route, jamais NaabigaPay directement.
@PostMapping("/api/pay")
public Map<String, String> creerLien(@RequestBody Map<String, Object> corps) throws Exception {
String commande = corps.get("commande").toString();
String body = json.writeValueAsString(Map.of(
"amount", corps.get("amount"), // entier, en FCFA
"reference", commande, // votre numéro de commande
"description", "Commande " + commande,
"return_url", "https://monsite.com/merci",
"single_use", true));
HttpRequest req = HttpRequest.newBuilder(URI.create(NPAY + "/payment-links"))
.header("Authorization", "Bearer " + SECRET)
.header("Content-Type", "application/json")
.header("Idempotency-Key", "cmd-" + commande) // évite un double lien si retry
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
String url = json.readTree(res.body()).get("url").asText();
return Map.of("url", url); // le front ouvre cette URL
}
}Pour un montant libre, remplacez amount par amount_min, amount_max et une description.
2.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). Recevez le corps en byte[] (octets intacts), recalculez le HMAC en hexadécimal et comparez en temps constant avec MessageDigest.isEqual.
package com.monsite.pay;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebhookController {
private static final byte[] WEBHOOK_SECRET =
System.getenv("NPAY_WEBHOOK_SECRET").getBytes(StandardCharsets.UTF_8);
private final ObjectMapper json = new ObjectMapper();
@PostMapping("/webhook")
public ResponseEntity<String> recevoir(
@RequestBody byte[] raw, // corps BRUT (octets exacts)
@RequestHeader("X-NPay-Signature") String signature,
@RequestHeader("X-NPay-Event") String event) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(WEBHOOK_SECRET, "HmacSHA256"));
byte[] brut = mac.doFinal(raw);
StringBuilder hex = new StringBuilder(brut.length * 2);
for (byte x : brut) hex.append(String.format("%02x", x));
String attendu = hex.toString();
// MessageDigest.isEqual : comparaison à temps constant.
boolean ok = MessageDigest.isEqual(
attendu.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
if (!ok) return ResponseEntity.status(400).body("signature invalide");
if ("payment.succeeded".equals(event)) {
// data.merchant_reference = VOTRE numéro de commande (la reference du lien).
String ref = json.readTree(raw).path("data").path("merchant_reference").asText();
marquerCommandePayee(ref);
}
return ResponseEntity.ok("ok");
}
}Astuce. Recevez le corps en byte[] et non en String : signez sur les octets exacts reçus, sinon un simple ré-encodage casse la vérification.
Référence rapide
| Méthode | Endpoint | Rôle |
|---|---|---|
| POST | /v1/payment-links | Cré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.