Payment Sessions
Une Payment Session réserve un numéro Mobile Money Kadryza vers lequel votre client envoie le paiement. La confirmation est asynchrone : Kadryza constate le crédit, le rapproche de la session, puis décide. Suivez le résultat par webhook ou en lisant la session.
- L’environnement est déduit de la clé (
kadryza_test_…→ session test,kadryza_live_…→ session live). N’envoyez jamaismerchant_id,is_testnienvironmentdans le corps. - Portées de clé requises :
write_transactionspour créer ou annuler,read_transactionspour lire.
Cycle de vie
| Statut | Signification | Ce que vous faites |
|---|---|---|
| AWAITING_PAYMENT | En attente du paiement du client | Afficher le numéro et le compte à rebours |
| SUCCESS | Paiement reçu et confirmé | Valider la commande |
| UNDER_REVIEW | Paiement reçu mais à vérifier, ou issue inconnue après l’expiration. Jamais confirmé automatiquement. | Garder la commande en attente, ne pas relancer de second paiement, contacter Kadryza |
| EXPIRED | Session test expirée sans paiement | Proposer un nouveau paiement |
| CANCELLED | Session test annulée | — |
Test et live ne se comportent pas pareil
| Situation | Session test | Session live |
|---|---|---|
| Crédit observé et rapproché | SUCCESS | UNDER_REVIEW : aujourd’hui, un paiement réel est d’abord vérifié |
| Aucun paiement à l’échéance | EXPIRED (15 min après expires_at) | UNDER_REVIEW : l’absence de preuve n’est pas une preuve d’échec |
Paiement arrivé après expires_at | UNDER_REVIEW | UNDER_REVIEW |
| Annulation | Possible tant que AWAITING_PAYMENT | Impossible (409) : le client a peut-être déjà payé |
| Mouvement de solde | Jamais | Uniquement au passage en SUCCESS |
Ne créez jamais une seconde session pour la même commande tant que la
première est AWAITING_PAYMENT ou UNDER_REVIEW : le client pourrait payer
deux fois.
Créer une Payment Session
/v1/payment-sessionsX-API-Key| Champ | Type | Requis | Description |
|---|---|---|---|
reference | string | Oui | Votre identifiant de commande, unique par environnement |
amount | integer | Oui | Montant en XAF, entier strictement positif |
currency | string | Oui | XAF |
operator | string | Oui | AIRTEL ou MOOV |
customer_phone | string | Live : oui · Test : non | Téléphone du payeur, au format international ; il renforce le rapprochement |
description | string | Non | Description courte |
metadata | object | Non | Stockée, mais jamais renvoyée dans les réponses ni les webhooks |
ttl_minutes | integer | Non | Durée de validité, de 1 à 60 minutes (défaut 10) |
return_url | string | Non | Adresse de votre site où renvoyer le client une fois le paiement réglé. URL absolue https (http://localhost accepté avec une clé de test), 2048 caractères au plus, sans identifiants. Voir Retour sur votre site |
pricing_version | integer | Non | 1 si votre intégration sait afficher le détail des frais et le total à payer. Requis si le tarif serveur ajoute une part de frais au payeur. |
Montant original, frais et total à payer
La tarification est calculée par le backend et figée à la création. Votre
intégration ne fournit jamais un taux, un montant de commission ou un mode de
prise en charge. amount dans la réponse et les instructions reste le total
à payer. Si présent, pricing.original_amount représente le montant original
de la commande, pricing.customer_fee les frais ajoutés au payeur, et
pricing.merchant_net_amount le net commercial avant autres coûts.
Une réponse peut inclure pricing même quand les frais sont désactivés. Les
valeurs calculées ne sont pas un solde disponible. fee_collection_status
distingue cotation non comptabilisée, commission comptabilisée, exemption,
statut inconnu et simulation sans argent réel. Utilisez les champs *_text
pour afficher exactement les montants dépassant la précision des nombres JS.
Sans pricing_version: 1, une création ajoutant des frais au payeur est refusée
avec 422 PRICING_VERSION_REQUIRED, avant toute réservation. Le mode marchand
et les frais désactivés ne demandent pas cette nouvelle capacité. Une modification
de tarif n’affecte jamais une session déjà créée. Ne recalculez pas ses frais
localement et ne considérez pas une cotation comme un paiement confirmé.
Le checkout public anonyme reçoit une projection payeur distincte : montant
original, frais client, total à payer, devise et leurs représentations exactes
*_text. Il ne reçoit ni taux interne, ni commission Kadryza, ni net marchand,
ni preuve de comptabilisation. Ces détails restent réservés aux réponses
authentifiées du marchand et à ses webhooks.
cURL
curl -X POST https://api.kadryza.app/v1/payment-sessions \
-H "X-API-Key: $KADRYZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference": "order_2026_001",
"amount": 5000,
"currency": "XAF",
"operator": "AIRTEL",
"customer_phone": "+23566000000"
}'{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "order_2026_001",
"ticket": "KDRZ-8F3K2",
"amount": 5000,
"currency": "XAF",
"operator": "AIRTEL",
"status": "AWAITING_PAYMENT",
"environment": "live",
"assigned_collection_number": "074000001",
"expires_at": "2026-06-05T12:40:00Z",
"created_at": "2026-06-05T12:30:00Z",
"instructions": "Envoyez 5000 XAF vers le numéro 074000001 (AIRTEL), puis conservez la référence KDRZ-8F3K2. La session expire si le paiement n'est pas reçu à temps.",
"checkout_url": "https://checkout.kadryza.com/pay/payment-sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}| Champ de réponse | Description |
|---|---|
assigned_collection_number | Le numéro Mobile Money que votre client doit payer |
ticket | Référence lisible à communiquer au client (KDRZ- + 5 caractères) |
expires_at | Fin de la fenêtre de paiement |
completed_at | Présent une fois la session conclue |
payment_proof | Preuve tirée du SMS de l’opérateur, dès que Kadryza le détient : operator_reference (référence de la transaction, le TID chez Airtel), payer_name (titulaire du compte, tel qu’écrit par l’opérateur), payer_phone_masked (+235 66 •• •• 01), received_at. Voir Preuve de paiement |
instructions | Phrase prête à afficher |
checkout_url | Page de paiement hébergée pour cette session. Lien de commodité, pas un secret. Le domaine peut être checkout.kadryza.com ou checkout.kadryza.app : n’en figez pas un seul dans une liste blanche. |
Les réponses n’exposent ni le téléphone complet du payeur (seulement sa forme
masquée dans payment_proof), ni metadata, ni aucun identifiant interne.
Preuve de paiement
Quand l’opérateur confirme un transfert, la passerelle Kadryza reçoit son SMS.
Kadryza en extrait payment_proof :
"payment_proof": {
"operator_reference": "PP260101.1200.A00001",
"payer_name": "IDRISS DEDE",
"payer_phone_masked": "+235 66 •• •• 01",
"received_at": "2026-10-01T18:54:10Z"
}operator_reference: votre client retrouve la même valeur dans son propre SMS. C’est la référence à donner à l’opérateur en cas de contestation.payer_name: le titulaire du compte qui a payé, tel qu’écrit par l’opérateur. Il peut différer de votre client si un proche a payé pour lui.- Les champs absents ne sont pas devinés : un SMS d’un format non vérifié ne donne que ce qui est sûr.
- Le SMS brut n’est jamais exposé.
Erreurs de création
| HTTP | code | Cause |
|---|---|---|
| 400 | INVALID_BODY, INVALID_TTL | Corps illisible, ttl_minutes hors 1–60 |
| 400 | INVALID_RETURN_URL | return_url qui n’est pas une adresse https absolue valide |
| 400 | INVALID_AMOUNT, INVALID_CURRENCY, INVALID_OPERATOR, REFERENCE_REQUIRED | Champ invalide |
| 400 | CUSTOMER_PHONE_REQUIRED | Session live sans customer_phone |
| 400 | PAYMENT_SESSION_AMOUNT_TOO_LOW, PAYMENT_SESSION_AMOUNT_TOO_HIGH | Montant hors des bornes de votre compte |
| 403 | LIVE_MODE_NOT_ALLOWED | Clé live, mais le mode live n’est pas activé pour votre compte |
| 403 | PAYMENT_SESSION_DAILY_LIMIT_EXCEEDED | Plafond journalier atteint |
| 409 | DUPLICATE_REFERENCE | reference déjà utilisée dans cet environnement |
| 501 | PROVIDER_NOT_IMPLEMENTED | Opérateur sans fournisseur implémenté |
| 503 | PROVIDER_RAIL_NOT_READY | Réseau d’encaissement de l’opérateur indisponible en live ; voir failed_checks |
| 503 | NO_COLLECTION_NUMBER_AVAILABLE | Aucun numéro disponible pour cet opérateur et cet environnement |
| 503 | PROVIDER_UNAVAILABLE | Aucun fournisseur actif pour cet opérateur |
Plus les erreurs communes d’authentification et de débit : Codes d’erreur.
{
"error": "payment provider rail is not ready for live payment sessions",
"code": "PROVIDER_RAIL_NOT_READY",
"operator": "AIRTEL",
"environment": "live",
"failed_checks": [
{
"name": "gateway_connected",
"ready": false,
"reason": "no matching gateway connected to hub",
"count": 0
}
]
}Face à PROVIDER_RAIL_NOT_READY ou NO_COLLECTION_NUMBER_AVAILABLE,
expliquez au client que cet opérateur est momentanément indisponible et
proposez l’autre opérateur, ou réessayez plus tard. N’affichez pas
failed_checks tel quel : il sert au diagnostic.
Récupérer une Payment Session
/v1/payment-sessions/:idX-API-KeyRenvoie la même vue que la création. Une session d’un autre compte ou d’un
autre environnement renvoie 404 NOT_FOUND.
curl https://api.kadryza.app/v1/payment-sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "X-API-Key: $KADRYZA_API_KEY"Si vous interrogez l’API en complément des webhooks : au plus une requête
toutes les 5 secondes pendant AWAITING_PAYMENT, et arrêt dès que le statut
change.
Retrouver une session par référence
/v1/payment-sessions/by-reference?reference=…X-API-KeyLe chemin de récupération après une création dont vous n’avez pas reçu la
réponse (coupure réseau, timeout). reference est obligatoire
(400 REFERENCE_REQUIRED) et limitée à 200 caractères (400 INVALID_REFERENCE).
curl "https://api.kadryza.app/v1/payment-sessions/by-reference?reference=order_2026_001" \
-H "X-API-Key: $KADRYZA_API_KEY"SDK : kadryza.paymentSessions.retrieveByReference('order_2026_001').
Historique des livraisons webhook
/v1/payment-sessions/:id/webhook-deliveriesX-API-KeyDiagnostic des webhooks envoyés pour une session, sans URL, corps ni réponse.
{
"deliveries": [
{
"event_id": "3f1c2b9a-7d4e-5a61-9b2f-0c8d7e6f5a41",
"endpoint_id": "d0fb9df9-0a9b-4c2f-84f8-e28d1a2b1e7c",
"event_type": "payment_session.under_review",
"status": "delivered",
"attempts": 1,
"last_http_status": 200,
"last_attempt_at": "2026-06-05T12:33:11Z",
"delivered_at": "2026-06-05T12:33:11Z",
"created_at": "2026-06-05T12:33:10Z"
}
]
}status : pending, retrying, delivered, failed, skipped (endpoint
désactivé), obsolete (la session a changé d’état avant l’envoi) ou unknown.
SDK : kadryza.paymentSessions.listWebhookDeliveries(id).
Annuler une Payment Session
/v1/payment-sessions/:id/cancelTest uniquementX-API-KeyUniquement pour une session test encore AWAITING_PAYMENT. Toute autre
session répond 409 NOT_CANCELLABLE, y compris une session live : elle a peut-être
déjà été payée.
curl -X POST https://api.kadryza.app/v1/payment-sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/cancel \
-H "X-API-Key: $KADRYZA_API_KEY"Votre propre interface de paiement
Vous pouvez garder votre interface et ne jamais afficher la page hébergée. Affichez alors :
amount,currency,operator;assigned_collection_number: le numéro à payer ;ticket, et un compte à rebours jusqu’àexpires_at.
Kadryza reste le moteur de paiement : constat du crédit, rapprochement et décision côté serveur. Rien de ce que le navigateur affiche ne fait foi.
Webhooks émis
| Événement | Quand |
|---|---|
payment_session.succeeded | La session passe en SUCCESS |
payment_session.under_review | La session passe en UNDER_REVIEW |
payment_session.expired | Une session test passe en EXPIRED |
Payload, en-têtes, signature et retries : Webhooks.