Docs › Quickstarts › Kotlin / Ktor
BackendIntégrer NaabigaPay en Kotlin / Ktor
Un client Ktor crée le lien de paiement, une route webhook vérifie la signature HMAC-SHA256, puis marque la commande payée.
Deux morceaux côté serveur : l'un 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 vos variables d'environnement et n'atteint jamais le navigateur.
1.Créer le lien
Votre front appelle votre route ; elle appelle POST/v1/payment-links avec la clé secrète via le client io.ktor.client, puis renvoie l'url de paiement.
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.*
const val NPAY = "https://api.pay.naabiga.com/api/v1"
val SECRET: String = System.getenv("NPAY_SECRET") // sk_live_... : reste côté serveur
val client = HttpClient(CIO) {
install(ContentNegotiation) { json() }
}
// Appelle NaabigaPay avec la clé secrète, renvoie l'URL de paiement.
suspend fun creerLien(commande: String, montant: Int): String {
val reponse = client.post("$NPAY/payment-links") {
header(HttpHeaders.Authorization, "Bearer $SECRET")
header("Idempotency-Key", "cmd-$commande") // évite un double lien si retry
contentType(ContentType.Application.Json)
setBody(buildJsonObject {
put("amount", montant) // entier, en FCFA
put("reference", commande) // votre numéro de commande
put("description", "Commande $commande")
put("return_url", "https://monsite.com/merci")
put("cancel_url", "https://monsite.com/panier")
put("single_use", true)
})
}
val corps = reponse.body<JsonObject>()
return corps["url"]!!.jsonPrimitive.content // le front ouvre cette URL
}La réponse contient url (ex. https://pay.naabiga.com/link/9f3c…) : la page où le client paie. 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). Lisez le corps brut avec call.receiveText(), recalculez le HMAC hexadécimal avec javax.crypto.Mac, comparez en temps constant, et ne créditez qu'ensuite.
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
val WEBHOOK_SECRET: String = System.getenv("NPAY_WEBHOOK_SECRET")
// HMAC-SHA256 du corps brut, rendu en hexadécimal.
fun hmacHex(secret: String, corps: String): String {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(), "HmacSHA256"))
return mac.doFinal(corps.toByteArray()).joinToString("") { "%02x".format(it) }
}
// Comparaison à temps constant (évite les attaques temporelles).
fun tempsConstant(a: String, b: String): Boolean {
if (a.length != b.length) return false
var diff = 0
for (i in a.indices) diff = diff or (a[i].code xor b[i].code)
return diff == 0
}
fun Route.webhookNaabiga() {
post("/webhooks/naabiga") {
val corps = call.receiveText() // corps BRUT : la signature porte dessus
val signature = call.request.headers["X-NPay-Signature"] ?: ""
val attendu = hmacHex(WEBHOOK_SECRET, corps)
if (!tempsConstant(signature, attendu)) {
call.respond(HttpStatusCode.BadRequest, "signature invalide")
return@post
}
val data = Json.parseToJsonElement(corps).jsonObject["data"]!!.jsonObject
if (call.request.headers["X-NPay-Event"] == "payment.succeeded") {
// data.merchant_reference = VOTRE numéro de commande (la reference du lien).
marquerCommandePayee(data["merchant_reference"]!!.jsonPrimitive.content)
}
call.respond(HttpStatusCode.OK)
}
}Astuce. Lisez le corps avec call.receiveText() avant tout décodage typé : la signature porte sur les octets bruts, un objet déjà désérialisé ne redonne pas la même chaîne.
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.