Appearance
Cash-in déprécié
Routes historiques du cash-in public. Elles restent servies pour les partenaires déjà en production. Toute nouvelle intégration utilise POST /checkout/sessions avec une clé sk_ : Référence API.
Chaque réponse porte :
http
Deprecation: true
Sunset: Thu, 30 Sep 2027 00:00:00 GMT
Link: </api/v1/checkout/sessions>; rel="successor-version"Les mêmes chemins existent sous /api et sous /api/v1. Les deux préfixes ont le même contrat. Les réponses réussies sont en 200, y compris les POST. Le corps est en camelCase. Il n'y a pas d'enveloppe { items }.
Base :
http
https://paymentdev-api.akieni.tech/api
https://paymentdev-api.akieni.tech/api/v1Authentification
POST /generate-application-access-token
Corps :
| Champ | Type | Obligatoire |
|---|---|---|
apiKey | string, 1-500 | Oui |
secretKey | string, 1-500 | Oui |
Réponse 200 :
| Champ | Type | Description |
|---|---|---|
token | string | Jeton oat_…, à envoyer en Authorization: Bearer |
expiresAt | string ISO 8601 | Expiration, 2 heures après l'émission |
Clé inconnue, secret incorrect, jeton expiré, révoqué ou application inactive : 401.
json
{ "message": "Unauthorized access", "status": 401, "code": "E_UNAUTHORIZED_ACCESS" }POST /revoke-application-access-token révoque uniquement le jeton présenté. Corps vide. Réponse 200 :
json
{ "message": "Your app session have been removed!" }Le couple apiKey / secretKey est délivré pour une application. Il ne remplace pas une clé sk_.
Montant
amount est un entier strictement positif, au plus 2147483647. Un nombre à virgule (10.5) est refusé. Pour le franc CFA, l'entier est le montant en francs : 1600 vaut 1 600 XAF.
Ce montant est le dû demandé. Il n'inclut pas les frais de service. Les frais apparaissent seulement dans totalAmount du contrôle de statut, une fois un encaissement engagé.
Ouvrir un checkout
POST /init-checkout
Jeton oat_ requis.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
externalNumber | string, 1-200 | Oui | Identifiant dossier côté partenaire |
amount | entier | Oui | Dû demandé |
description | string, max 2000 | Non | Libellé, repris dans le webhook (note) |
type | ecommerce ou service | Non | Type de paiement |
items | tableau, 1 à 50 | Oui | Lignes. number unique dans le tableau |
customer | objet | Non | Si présent : full_name, address, phone requis |
merchant | objet | Non | URLs de retour et webhook |
Ligne items : number (1-64), name (1-200), price (nombre ≥ 0), quantity (nombre ≥ 0).
merchant :
| Champ | Description |
|---|---|
redirectUrl | Retour après succès. Origine autorisée pour l'application. |
backUrl | Retour après abandon. Même règle d'origine. |
webhookUrl | URL publique https du partenaire. En production, http est refusé. |
coverUrl | Visuel, non utilisé pour l'appel sortant. |
note | Note, max 2000 caractères. |
Une URL de webhook avec identifiants (user:pass@hôte), un hôte local ou une adresse privée est refusée avant toute création de paiement.
Réponse 200, clés exactes :
| Champ | Description |
|---|---|
paymentId | Identifiant entier du paiement |
externalNumber | Echo du dossier |
token | Jeton de formulaire du checkout. Ce n'est pas un oat_. |
redirectUrl | Page de checkout hébergée |
Tant que le paiement est encore created (aucun encaissement engagé), un nouvel init-checkout avec le même externalNumber met à jour la même ligne, y compris le montant, et conserve la même redirectUrl.
Si le paiement est déjà pending, completed ou failed, le même externalNumber ouvre un nouveau paiement et un nouveau débit.
Le paiement par carte compatible avec une page hébergée passe par init-checkout, pas par partners/payments.
Débit partenaire
POST /partners/payments
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
externalNumber | string, 1-200 | Oui | Identifiant dossier |
amount | entier | Oui | Dû demandé |
description | string, max 2000 | Non | Libellé |
provider | string, 1-32 | Oui | mtn, airtel, visa, mastercard |
customer | objet | Conditionnel | phone obligatoire pour mtn et airtel |
items | tableau, 1 à 50 | Oui | Mêmes règles que init-checkout |
Téléphone : format international +242…, ou numéro local 0 suivi de 8 à 14 chiffres, converti avec l'indicatif +242. Exemple : 056479451 devient +24256479451. Numéro invalide : 422.
json
{ "message": "Invalid phone number", "status": 422, "code": "INVALID_PHONE_NUMBER" }Réponse 200, clés exactes : paymentId, number, externalNumber, status, amount, provider.
number suit T + année sur 2 chiffres + mois sur 2 chiffres + 6 chiffres (T2609000001). status vaut pending. amount est le dû demandé, pas le total frais inclus. Cette réponse ne contient pas de redirectUrl.
bgfi et tout autre fournisseur : 404.
json
{ "message": "Payment provider not found", "status": 404, "code": "PROVIDER_NOT_FOUND" }Fournisseur reconnu mais non activé pour l'application : 403 PROVIDER_NOT_CONFIGURED.
Si l'opérateur de paiement échoue avant qu'un encaissement existe : 502 GATEWAY_REQUEST_FAILED. Si un encaissement est déjà pending ou accepté, la réponse reste 200 avec status: pending.
Contrôle de statut
GET /transactions/{number}/check-status
number est le numéro T… du paiement, pour l'application authentifiée. Inconnu : 404.
json
{ "message": "Row not found", "status": 404, "code": "E_ROW_NOT_FOUND" }Plusieurs paiements partagent parfois le même externalNumber. Le contrôle par numéro vise une ligne. Il n'y a pas de recherche par externalNumber.
| Champ | Description |
|---|---|
id | paymentId |
number | Numéro T… |
externalNumber | Dossier partenaire |
status | created, pending, completed, failed, cancelled |
paidAmount | Dû demandé |
totalAmount | Dû demandé tant qu'aucun encaissement n'est engagé, puis dû + frais |
paidAt | Date ISO si completed, sinon null |
description, type | Echo de la création |
customer | Echo, ou null |
transactionItems | Echo des lignes |
paymentGateway | { id, name, description, logo } ou null si aucun fournisseur |
financialTransactionId | Référence opérateur, ou null |
paymentIntentId, extraInfo, paymentInfo et paymentFailedReason valent null.
| Situation | status |
|---|---|
| Checkout ouvert, aucun encaissement | created |
| Encaissement en cours ou déjà accepté | pending |
| Seul un encaissement en échec, checkout ouvert | failed |
| Paiement terminé | completed |
| Checkout annulé | cancelled |
| Checkout expiré | failed |
Passerelles
GET /partners/payment-gateways
Réponse 200 : un tableau, éventuellement vide. Pas d'objet enveloppe.
id | name | Téléphone requis |
|---|---|---|
| 1 | mtn | Oui |
| 2 | airtel | Oui |
| 3 | visa | Non |
| 4 | mastercard | Non |
Une passerelle n'apparaît que si le canal correspondant est activé. commissionPercentage est un ratio (0.015 pour 1,5 %). commissionValue est un montant fixe dans la même unité que amount. logo vaut null.
Webhook partenaire
Après un paiement completed, ou lorsque le statut notifié est failed, Epayment envoie POST JSON à l'URL de webhook courante de l'application. L'URL enregistrée sur le paiement sert de repli.
En-tête envoyé : Content-Type: application/json seulement. Pas de signature, pas de Authorization.
| Champ | Description |
|---|---|
paymentId | Identifiant entier |
externalNumber | Dossier partenaire, sans suffixe interne |
amount | Dû demandé (paidAmount) |
status | completed ou failed |
paidAt | Date ISO si completed, sinon null |
note | description, ou null |
Un checkout cancelled ou expired part en webhook avec status: failed. Le contrôle de statut, lui, distingue encore cancelled.
Les statuts created et pending ne déclenchent pas d'appel. Un même statut terminal n'est renvoyé qu'après une réponse 2xx. Un échec d'appel ne change pas l'état du paiement.
Erreurs
Validation (422), sans code d'enveloppe ministry :
json
{
"message": "Validation failed",
"errors": [{ "message": "Expected integer", "rule": "type", "field": "amount" }]
}rule vaut type, range, enum, distinct, url ou required. Les autres erreurs ont exactement message, status et code.
| Code | HTTP | Situation |
|---|---|---|
E_UNAUTHORIZED_ACCESS | 401 | Clé, secret ou jeton refusé |
E_ROW_NOT_FOUND | 404 | Numéro inconnu pour cette application |
PROVIDER_NOT_FOUND | 404 | provider hors mtn, airtel, visa, mastercard |
PROVIDER_NOT_CONFIGURED | 403 | Fournisseur non activé |
INVALID_PHONE_NUMBER | 422 | Téléphone inutilisable |
GATEWAY_REQUEST_FAILED | 502 | Échec opérateur avant tout encaissement |
E_TOO_MANY_REQUESTS | 429 | Quota dépassé |
E_INTERNAL_ERROR | 500 | Erreur interne |
Quotas, par identifiant d'accès et par chemin, sur une minute : 5 requêtes pour le jeton, la révocation, init-checkout et partners/payments ; 30 requêtes pour le contrôle de statut et la liste des passerelles.
Correspondance
| Cash-in déprécié | Checkout ministry |
|---|---|
apiKey + secretKey, puis oat_ | Authorization: Bearer sk_… |
externalNumber | order_id |
amount entier | amount en unité mineure (XAF : même entier) |
redirectUrl de init-checkout | checkout_url |
GET /transactions/{number}/check-status | GET /checkout/sessions?order_id= |
Webhook non signé completed / failed | Webhook signé checkout.completed et statuts terminaux |