Aller au contenu principal

Paiement direct

Votre serveur déclenche le paiement avec le numéro du client et le montant, sans passer par la page d'Integral Pay. Le client confirme sur son téléphone.

Toujours une Idempotency-Key

Envoyez un en-tête Idempotency-Key unique par paiement. En cas de coupure réseau, vous pourrez renvoyer exactement la même requête sans débiter deux fois votre client. Voir Idempotence.

POST /api/v1/direct-payments/

ChampTypeObligatoireRègle
providertexteouiorange_bf, moov_bf ou sank_money_bf, parmi les moyens actifs de votre projet
phonetexteoui70123456, 22670123456 ou +22670123456
dial_codetextenonIndicatif sans « + » ; défaut 226
amountentierouiDe 100 à 2 000 000 XOF, ou le plafond de votre projet
otptexteOrange et SankCode de confirmation du client, chiffres uniquement
referencetextenonVotre référence (numéro de commande), 100 caractères au maximum
descriptiontextenon255 caractères au maximum
metadataobjetnonDonnées libres, renvoyées telles quelles

Déroulement selon l'opérateur​

OpérateurCe que fait le clientRéponse habituelle
Orange MoneyGénère un code OTP depuis son menu Orange Money et le saisit sur votre site201 réussi
Moov MoneyReçoit une demande de confirmation sur son téléphone, rien à saisir202 en attente : attendez le webhook
Sank MoneyReçoit un code par SMS (étape préalable ci-dessous) et le saisit sur votre site201 réussi

Sank Money, étape préalable. Appelez POST /api/v1/direct-payments/sank-otp/ avec phone, amount et éventuellement dial_code : le client reçoit son code par SMS. Transmettez-le ensuite dans otp. Les envois de SMS sont limités par numéro.

Exemple​

direct_payment.py
# Même clé pour tous les renvois d'une même tentative ; nouvelle tentative = nouveau numéro
idempotency_key = f"commande-{order.id}-essai-{order.payment_attempts}"

resp = session.post(
f"{API_BASE}/direct-payments/",
headers={"Idempotency-Key": idempotency_key},
json={
"provider": "orange_bf",
"phone": "70123456",
"amount": 5000,
"otp": otp_saisi_par_le_client,
"reference": f"CMD-{order.id}",
},
timeout=60,
)
payment = resp.json()
if resp.status_code == 201:
... # réussi
elif resp.status_code == 202:
... # en attente : le webhook confirmera
elif resp.status_code == 402:
... # refusé : afficher payment["message"]
Conservez la clé d'idempotence avec la commande

Générez la clé une fois par tentative de paiement et enregistrez-la avant l'appel. Si l'appel échoue sans réponse (délai dépassé), renvoyez la requête avec la même clé : vous obtenez le paiement déjà créé, sans second débit.

Réponse​

HTTPstatusSignification
201SUCCEEDEDPaiement confirmé
202PENDINGEn attente de la confirmation du client
402CANCELEDRefusé par l'opérateur ; message donne la raison
response.json
{
"id": "3f2a9c1e-5b7d-4e8a-9c21-7d4e5f6a8b90",
"status": "SUCCEEDED",
"amount": "5000.00",
"currency": "XOF",
"phone": "22670123456",
"provider": "orange_bf",
"provider_reference": "OM2611061015300001",
"reference": "CMD-1234",
"fee_amount": "150.00",
"message": "Paiement confirmé.",
"test": false,
"created_at": "2026-11-06T10:15:30+00:00"
}

fee_amount est la commission prélevée ; votre solde est crédité du montant net. Le statut se consulte ensuite avec GET /api/v1/direct-payments/{id}/, qui interroge l'opérateur si le paiement est encore en attente.