Contrat API — Activer ma boutique TiketMOMO (un seul appel)
Date : 2026-10-25 · Destinataires : dev app mobile, dev PWA · API : https://live.jmoai.net
1. À quoi ça sert
Un unique appel qui aligne toute la configuration TiketMOMO d’un vendeur et dit, étape par étape, ce qui manque encore. C’est le bouton « Activer ma boutique ».
Avant cet endpoint, l’app devait enchaîner plusieurs appels (déclaration NAS, synchro des forfaits, clé FedaPay, stock, portail) sans jamais savoir laquelle bloquait la vente. Ici, un seul appel orchestre tout, corrige ce qui peut l’être automatiquement, et renvoie un verdict clair avec la prochaine action à faire.
Il règle les erreurs de configuration classiques : routeur rattaché au mauvais compte, NAS mal aligné, forfaits non synchronisés, stock vide, portail/walled-garden absent, clé marchande manquante.
2. Endpoint
POST /api/v1/clients/tiketmomo/activate-store
- Auth : Bearer (client connecté), rôle client, compte lié. Sans jeton →
401. - Validation : corps validé par schéma Joi (
stripUnknown). Un corps invalide →4xx. - Code HTTP : toujours
200quand l’appel est authentifié et valide — même quand la boutique n’est pas prête. Le verdict est porté parstatusetsteps, pas par le code HTTP. - Idempotent : relancer n’duplique aucune allocation ni configuration. L’app peut le rappeler autant que nécessaire (ex. après chaque correction).
- Réponse rapide (< 5 s) : les tâches lourdes (remplissage de stock en mode VPN) se poursuivent en arrière-plan.
3. Corps de la requête
{
"router": {
"serial": "HEW0975P75W", // REQUIS — numéro de série du routeur
"allocation_id": "uuid", // optionnel (ou "", null, "unknown")
"model": "hAP ax lite", // optionnel
"routeros_version": "7.x", // optionnel
"identity": "…", // optionnel
"mac": "…", // optionnel
},
"nas": {
// optionnel (défaut {})
"declared_nas_name": "hotspot1-TM1390",
"hotspot_servers": ["hotspot1"],
"local_ip": "192.168.x.x",
"dns_name": "sadel.sn",
"walled_garden_present": true,
},
"profiles": [/* forfaits, même format que la déclaration NAS */],
"fedapay_public_key": "pk_live_…", // optionnel — persistée si fournie
"request_id": "…", // optionnel (traçabilité)
}
Seul router.serial est obligatoire. Les autres champs enrichissent l’alignement ; absents, les
étapes correspondantes remonteront simplement « à faire ».
4. Réponse
{
"status": "ACTIVE", // ACTIVE | ACTION_REQUIRED | REFUSED
"mode": "VPN", // VPN (serveur pilote le routeur) | APP_BRIDGE (passerelle locale)
"allocation_id": "uuid|null",
"store": {
"slug": "bocar-daouda-deme-client-a",
"url": "https://bocar-daouda-deme-client-a.wifi.jmoai.net",
"webhook_url": "https://…", // webhook agrégateur du vendeur
},
"steps": [
{ "key": "LICENCE", "ok": true, "code": "LICENCE_VALID", "message_fr": "…" },
{
"key": "STOCK",
"ok": false,
"code": "STOCK_REQUIRED",
"message_fr": "…",
"action_for_app": {
"action": "SYNC_VOUCHERS",
"endpoint": "POST /api/v1/clients/vouchers/sync",
"…": "…",
},
},
// …
],
"next_action": {
"who": "NONE", // NONE | APP | SELLER
"code": "STORE_READY",
"message_fr": "Votre boutique est active et prête à vendre.",
"action": {/* présent seulement si who = APP */},
},
"green_light": { "green": true, "checks": 6, "warnings": 1 },
"warnings": [
{ "code": "MIN_3_OFFERS_STOCKED", "ok": false, "detail": "Moins de trois forfaits sont suffisamment stockés." }
],
}
La même charge est aussi renvoyée dupliquée sous la clé
data(compatibilité app). Lire indifféremment la racine oudata.
status
| Valeur | Sens | Qui agit (next_action.who) |
|---|---|---|
ACTIVE |
Feu vert : la boutique peut vendre maintenant | NONE |
ACTION_REQUIRED |
Il reste au moins une étape à régler | APP ou SELLER |
REFUSED |
Blocage dur (licence absente, routeur à un autre compte) | SELLER |
mode
VPN— un tunnel actif existe pour ce serial : le serveur pilote le routeur (synchro forfaits, stock à la volée, lecture de santé). Le stock se remplit automatiquement en fond.APP_BRIDGE— pas de tunnel : c’est l’app, sur le même réseau que le routeur, qui doit aligner le NAS et pousser le stock. Voir aussiCONTRAT-API-ALIGNEMENT-MANUEL.
5. Les étapes (steps[])
Chaque étape : { key, ok, code, message_fr }, parfois warning ou action_for_app.
Ordre : la licence d’abord, le feu vert en dernier.
key |
code si OK |
code si à faire |
Ce que ça vérifie / corrige |
|---|---|---|---|
LICENCE |
LICENCE_VALID |
LICENCE_REQUIRED |
Licence TiketMOMO (voucher_sales ou J+RADIUS) active. Sinon → REFUSED. |
STORE_SLUG |
STORE_SLUG_RESOLVED |
— | Crée/réutilise le profil boutique et son slug. |
ROUTER_OWNERSHIP |
ROUTER_OWNERSHIP_VERIFIED |
ROUTER_OWNED_BY_OTHER_ACCOUNT |
Verrou cross-compte strict sur le serial. Sinon → REFUSED. |
PAYMENT_KEY |
PAYMENT_KEY_VALID |
— | Clé marchande FedaPay (fournie ou stockée). warning: MERCHANT_KEY_ALREADY_IN_USE si clé partagée (acceptée). |
NAS_NAME |
NAS_NAME_ALIGNED |
NAS_SEQUENCE_MISSING |
Aligne le nom NAS déclaré sur la séquence attendue. |
PROFILES |
PROFILES_SYNCED |
PROFILES_NOT_SYNCED |
Synchronise les forfaits (prix/durée) envoyés. |
STOCK |
STOCK_READY |
STOCK_REQUIRED |
Stock de tickets suffisant. En mode VPN : se remplit en fond (STOCK_FILLING). En APP_BRIDGE : action_for_app = SYNC_VOUCHERS. |
PORTAL |
PORTAL_DEPLOYED |
PORTAL_INSTALL_REQUIRED |
Portail captif + script walled-garden installés. Sinon action_for_app = INSTALL_PORTAL. |
WEBHOOK |
WEBHOOK_URL_READY |
— | URL de webhook agrégateur du vendeur prête. |
GREEN_LIGHT |
STORE_READY_TO_SELL |
STORE_NOT_READY |
Verdict final strict (evaluateGreenLight). |
Le stock n’est pas une condition pour le mode VPN lorsque le serveur peut livrer directement.
Dans ce cas l’étape STOCK peut rester STOCK_FILLING, mais ne change pas status: ACTIVE si
GREEN_LIGHT est vert. En APP_BRIDGE, la vente requiert au moins un ticket disponible ; sans
stock, GREEN_LIGHT renvoie ACTION_REQUIRED avec l’action SYNC_VOUCHERS. Les avertissements
non bloquants du verdict sont aussi exposés dans warnings[] à la racine (et dans data).
action_for_app
Quand une étape attend un geste de l’app, elle porte un action_for_app que next_action recopie
si c’est la première action bloquante :
SYNC_VOUCHERS→POST /api/v1/clients/vouchers/sync(pousser les tickets,count_per_offer,site_id,current_stock).INSTALL_PORTAL→GET /api/v1/clients/portal-editor/download-pack+GET /api/v1/clients/tiketmomo/walled-garden-script.
6. Comment l’app s’en sert (boucle « un clic »)
- L’utilisateur appuie sur « Activer ma boutique » →
POST …/activate-storeavec le serial (et ce que l’app connaît : NAS, forfaits, clé). - Lire
status:ACTIVE→ afficher « Boutique prête à vendre ».REFUSED→ affichernext_action.message_fr(souscrire une licence, contacter le support).ACTION_REQUIRED→ exécuternext_action.actionsiwho = APP(synchro tickets, pose du portail…), puis rappeler le même endpoint. Siwho = SELLER, afficher le message.
- Répéter jusqu’à
ACTIVE. L’appel étant idempotent, cette boucle est sûre.
7. Vérifier sans activer
Pour n’afficher l’état sans rien aligner (écran boutique, rafraîchissements) :
GET /api/v1/clients/store/tiketmomo/readiness— toutes les pièces manquantes en un appel (voirCONTRAT-API-BOUTIQUE-TIKETMOMO).GET /api/v1/clients/store/points— feu vertalignment.ready_to_sellpar routeur (voirCONTRAT-API-PRET-A-VENDRE).
8. Configuration du pays (évite le mauvais agrégateur)
Le pays de la boutique décide de l’agrégateur d’encaissement (FedaPay au Bénin, FeexPay en
Côte d’Ivoire / Sénégal / Burkina / Togo / Congo) et des bons opérateurs affichés à l’acheteur.
Voir CONTRAT-API-PAYS-BOUTIQUE et CONTRAT-API-TIKETMOMO-AGREGATEURS.
Repli de sécurité au checkout (depuis le 2026-10-25). Si le pays de la boutique est vide,
le backend ne retombe plus bêtement sur le Bénin : il le résout dans l’ordre
paramètre d'appel → payment.country de la boutique → pays du compte de retrait → indicatif
téléphonique du compte de retrait → indicatif du gérant → Bénin en tout dernier recours (avec un
avertissement journalisé). But : un vendeur d’un autre pays n’atterrit plus par erreur sur FedaPay
Bénin. Renseigner le pays reste la bonne pratique ; le repli n’est qu’un filet.
9. Codes de refus (REFUSED)
code |
Signification | Action vendeur |
|---|---|---|
LICENCE_REQUIRED |
Pas de licence TiketMOMO active | Souscrire une licence |
ROUTER_OWNED_BY_OTHER_ACCOUNT |
Serial déjà rattaché à un autre compte | Contacter le support |
Les autres étapes non satisfaites donnent ACTION_REQUIRED (pas un refus) : elles se corrigent
par une action de l’app ou du vendeur, puis un nouvel appel.