Échange entre portefeuilles
Une personne paie X F CFA depuis un portefeuille et reçoit X − frais sur son autre portefeuille, sur le même numéro de téléphone : Orange Money → Wave, ou Wave → Orange Money. Même monnaie, aucune conversion : Xaalis encaisse sur un réseau et verse sur l’autre.
Ce sont des endpoints publics (sans clé d’API), comme la page de paiement hébergée : ils sont derrière la page
d’échange Xaalis. Chaque échange est protégé par son propre secret client cs, renvoyé une seule fois à la création.
Le parcours
Section intitulée « Le parcours »sequenceDiagram
participant P as Personne (page d'échange)
participant X as API Xaalis
P->>X: POST /v1/public/exchanges/quotes (from, montant, téléphone)
X-->>P: devis exq_… (frais, receive_amount, valable 5 min)
P->>X: POST /v1/public/exchanges/otp (téléphone)
P->>X: POST /v1/public/exchanges (quote, otp) + Idempotency-Key
X-->>P: échange exc_… + checkout_url + cs
P->>X: paie sur la page hébergée (portefeuille source)
X-->>P: versement sur l'autre portefeuille, même numéro
P->>X: GET /v1/public/exchanges/{id}?cs=… jusqu'au statut final
1. Devis
Section intitulée « 1. Devis »curl -s -X POST https://sandbox-api.xaalis.org/v1/public/exchanges/quotes \ -H "Content-Type: application/json" \ -d '{ "from": "orange_money", "send_amount": 10000, "phone": "+221771234567" }'{ "object": "exchange_quote", "id": "exq_…", "from": "orange_money", "to": "wave", "send_amount": 10000, "fee": 150, "receive_amount": 9850, "currency": "XOF", "expires_at": "…" }- Donnez
send_amount(ce qui quitte le portefeuille source) oureceive_amount(ce qui doit arriver). Pour un montant reçu, Xaalis cherche le plus petit montant envoyé qui donne exactement ce montant ; s’il n’existe pas, vous obtenez422 limit_exceeded. toest toujours l’autre réseau ; le même réseau des deux côtés est refusé.- Un devis dure 5 minutes et ne peut financer qu’un échange.
2. Confirmer le numéro
Section intitulée « 2. Confirmer le numéro »curl -s -X POST https://sandbox-api.xaalis.org/v1/public/exchanges/otp \ -H "Content-Type: application/json" -d '{ "phone": "+221771234567" }'En mode test, aucun SMS n’est envoyé : la réponse contient "test_code": "000000", et 000000 est le seul code
accepté. Au plus 5 codes par numéro et par heure (429 rate_limited au-delà).
3. Créer l’échange
Section intitulée « 3. Créer l’échange »curl -s -X POST https://sandbox-api.xaalis.org/v1/public/exchanges \ -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \ -d '{ "quote": "exq_…", "otp": "000000" }'{ "object": "exchange", "id": "exc_…", "status": "awaiting_payment", "from": "orange_money", "to": "wave", "send_amount": 10000, "fee": 150, "receive_amount": 9850, "currency": "XOF", "livemode": false, "created_at": "…", "checkout_url": "https://sandbox.xaalis.org/…", "cs": "…" }- L’
Idempotency-Keyest obligatoire ; relancer avec la même clé renvoie le même échange. - Envoyez la personne sur
checkout_urlpour payer depuis le portefeuille source. Elle a 15 minutes ; ensuite l’échange passe àexpiredet rien ne bouge. - Gardez
cscôté serveur ou dans la mémoire de la page uniquement : c’est le seul moyen de lire l’échange.
4. Suivre l’échange
Section intitulée « 4. Suivre l’échange »curl -s "https://sandbox-api.xaalis.org/v1/public/exchanges/exc_…?cs=$CS"Interrogez tant que le statut n’est pas final. La réponse ne contient jamais de détail du grand livre, du marchand ou
de l’opérateur, et elle est envoyée avec Cache-Control: no-store.
| Statut | Ce que cela veut dire pour la personne |
|---|---|
awaiting_payment |
on attend son paiement sur la page hébergée |
paid |
paiement reçu ; le versement va démarrer |
payout_processing |
l’argent est en route vers l’autre portefeuille |
completed |
final — receive_amount est arrivé sur l’autre portefeuille |
expired |
final — pas payé en 15 minutes ; rien n’a été prélevé |
refunding |
le versement a échoué ; le send_amount complet est en cours de restitution |
refunded |
final — le send_amount complet est revenu sur le portefeuille source (frais compris) |
needs_review |
un opérateur Xaalis vérifie l’échange — l’argent est en sécurité et la personne sera contactée |
Seul un paiement confirmé par l’opérateur compte. Arriver sur une page « succès » ne prouve rien.
frais = max(100, ⌈send_amount × 1,5 %⌉) F CFA, arrondis au franc supérieur ; receive_amount = send_amount − frais.
| Envoie | Frais | Reçoit |
|---|---|---|
| 1 000 | 100 | 900 |
| 10 000 | 150 | 9 850 |
| 33 333 | 500 | 32 833 |
| 200 000 | 3 000 | 197 000 |
Si le versement échoue, les frais sont restitués avec le reste : un échange remboursé ne coûte rien à la personne.
| Par défaut | |
|---|---|
| Par échange | 1 000 – 200 000 F CFA |
| Par numéro, sur 24 h glissantes | 500 000 F CFA |
| Les 7 premiers jours d’un numéro | 50 000 F CFA par échange, 100 000 F CFA par 24 h |
| Échanges par numéro et par heure | 5 |
Au-delà d’une limite → 422 limit_exceeded (vérifié au devis, puis de nouveau à la création). Si le portefeuille de
destination n’a pas assez de réserve pour le versement → 503 liquidity_unavailable : réessayez plus tard ou avec un
montant plus petit.
Même personne des deux côtés
Section intitulée « Même personne des deux côtés »Le numéro qui paie est le numéro qui reçoit : le versement part toujours vers le numéro du devis, sur l’autre réseau. Il est impossible d’envoyer un échange à quelqu’un d’autre.
Déclenchez l’encaissement sans portefeuille, avec le cs de l’échange :
curl -s -X POST "https://sandbox-api.xaalis.org/v1/public/exchanges/exc_…/simulate?cs=$CS" \ -H "Content-Type: application/json" -d '{ "outcome": "succeeded" }'succeeded mène l’échange jusqu’à completed ; failed refuse l’encaissement (l’échange ne verse jamais rien).
| HTTP | code |
Quand |
|---|---|---|
| 400 | validation_error |
champ invalide, numéro qui n’est pas +2217XXXXXXXX, mauvais code OTP |
| 403 | forbidden |
appel sur une API où le mode test est coupé (production) — utilisez sandbox-api.xaalis.org |
| 404 | not_found |
échange inconnu, ou mauvais cs |
| 409 | invalid_state |
le devis a expiré ou a déjà servi |
| 422 | limit_exceeded |
montant hors des limites ci-dessus |
| 429 | rate_limited |
trop de requêtes ou de codes OTP — attendez Retry-After secondes |
| 503 | liquidity_unavailable |
pas assez de réserve sur le réseau de destination pour l’instant |
Les schémas complets sont dans la référence de l’API, rubrique Wallet exchange.