Documentation J+SERVICES Guides Référence API

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

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 ou data.

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

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 :

6. Comment l’app s’en sert (boucle « un clic »)

  1. L’utilisateur appuie sur « Activer ma boutique » → POST …/activate-store avec le serial (et ce que l’app connaît : NAS, forfaits, clé).
  2. Lire status :
    • ACTIVE → afficher « Boutique prête à vendre ».
    • REFUSED → afficher next_action.message_fr (souscrire une licence, contacter le support).
    • ACTION_REQUIRED → exécuter next_action.action si who = APP (synchro tickets, pose du portail…), puis rappeler le même endpoint. Si who = SELLER, afficher le message.
  3. 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) :

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.