Documentation J+SERVICES Guides Référence API

Contrat API — État de la boutique TiketMOMO (app mobile)

Date : 2026-08-13 Pour : l’app mobile Endpoint : GET /api/v1/clients/store/tiketmomo/readiness


1. À quoi ça sert

Répondre à une seule question, avant que le gérant ne se déplace : sa boutique peut-elle vendre et livrer, maintenant ?

Sans cet appel, l’app pose le portail, essaie, échoue, recommence — et personne ne sait quelle pièce manque. Cet état donne la liste complète des pièces manquantes en un appel.

2. Quand l’appeler

Ne pas le mettre en cache long : le stock bouge à chaque vente.

3. Réponse

Toujours 200, y compris quand la boutique n’est pas prête — ce n’est pas une erreur d’appel, c’est un constat.

{
  "success": true,
  "data": {
    "can_sell": false,
    "state": "NO_STOCK",
    "blocking_reason": "Stock vide : créez des tickets avant d’encaisser, sinon vos clients paieront sans rien recevoir.",

    "delivery": {
      "possible": false,
      "source": "NONE", // STOCK | RADIUS | DIRECT | NONE
      "reason": "tunnel disconnected : la frappe directe est impossible",
    },

    "checks": [
      { "code": "NO_LICENSE", "ok": true, "detail": null, "action": null },
      { "code": "NO_STORE_PROFILE", "ok": true, "detail": null, "action": null },
      { "code": "NO_SLUG", "ok": true, "detail": null, "action": null },
      { "code": "NO_OFFERS", "ok": true, "detail": null, "action": null },
      { "code": "ROUTER_NOT_LINKED", "ok": true, "detail": null, "action": null },
      {
        "code": "NO_STOCK",
        "ok": false,
        "detail": "stock vide",
        "action": "Stock vide : créez des tickets…",
      },
      {
        "code": "DELIVERY_IMPOSSIBLE",
        "ok": false,
        "detail": "tunnel disconnected…",
        "action": "Stock vide et routeur injoignable…",
      },
    ],

    "store_slug": "chez-moussa",
    "plan_code": "TIKETMOMO_PRO",
    "expires_at": "2027-01-31T23:59:59Z",
    "offers_count": 5,
    "vouchers_available": 0,
    "access_mode": "VPN",
    "radius_refill_available": false,
    "router": {
      "allocation_id": "uuid",
      "serial": "HH40AF7YVNP",
      "tunnel_status": "DISCONNECTED",
      "tunnel_last_seen_at": "2026-08-13T14:02:11Z",
    },
  },
}

4. La règle d’affichage qui compte

Afficher checks en entier, pas seulement blocking_reason.

Montrer un problème à la fois est exactement ce qui fait recommencer trois fois : le gérant corrige, réessaie, découvre le suivant, se redéplace. Une liste à cocher, tout de suite, lui évite les allers-retours.

blocking_reason sert au bandeau d’en-tête ; checks[].action remplit la liste. Chaque action est déjà rédigée en français, prête à afficher — ne pas la reformuler à partir du code.

5. delivery — la seule question qui décide si encaisser est sûr

Une vente encaissée mais non livrable laisse un client payé sans rien recevoir. delivery.source dit par où le ticket arrivera :

source Sens
STOCK des tickets sont déjà en réserve
RADIUS le serveur RADIUS regarnira le stock, sans passer par le routeur
DIRECT le ticket sera frappé sur le MikroTik au moment de la vente
NONE aucune voie — ne pas encaisser

Un vouchers_available: 0 n’est pas un blocage en soi : avec un tunnel connecté ou une licence RADIUS, le ticket sera créé à la volée. C’est delivery.possible qui fait foi, pas le compteur de stock.

6. Les états

state Ce que le gérant doit faire
READY rien, la boutique vend
NO_LICENSE aucune licence n’autorise la vente de tickets
NO_STORE_PROFILE ouvrir l’éditeur de portail et enregistrer la boutique une première fois
NO_SLUG choisir le nom public de la boutique
NO_OFFERS ajouter des forfaits, ou générer des tickets (ils servent alors de catalogue)
ROUTER_NOT_LINKED rattacher un routeur
NO_STOCK générer des tickets — aucune autre voie de livraison n’est disponible
DELIVERY_IMPOSSIBLE remettre le routeur en ligne, ou créer du stock

Les états sont ordonnés par dépendance : on nomme la cause la plus profonde. Inutile de reprocher un stock vide à quelqu’un qui n’a pas encore de boutique.

7. À ne pas faire

8. Le portefeuille — deux populations de vendeurs

GET /api/v1/store/tiketmomo/metrics renvoie, sous withdrawal_status, de quoi construire l’écran du portefeuille :

"withdrawal_status": {
  "available_balance": 0,
  "pending_balance": 0,
  "minimum_withdrawal": 1000,
  "withdrawals_enabled": true,      // gel administrateur — indépendant du mode ci-dessous
  "payout_mode": "MERCHANT",        // "PLATFORM" | "MERCHANT" | "NONE"
  "can_request_withdrawal": false,
  "payout_provider": "fedapay",     // slug de l'agrégateur, ou null si inconnu
  "payout_badge": "FedaPay",        // libellé prêt à afficher
  "payout_dashboard_url": "https://live.fedapay.com",   // peut être null
  "payout_notice": "Vos ventes sont encaissées directement sur votre compte FedaPay. Le retrait se fait depuis votre tableau de bord FedaPay."
}

Ce que chaque mode veut dire

payout_mode L’argent est… Écran attendu
PLATFORM sur notre compte : nous le détenons, nous le lui devons bouton de retrait normal
MERCHANT déjà chez lui, sur son compte d’agrégateur badge + renvoi, pas de bouton
NONE aucune vente réussie : rien à trancher encore bouton normal

Ventes, solde et comptabilité (jour / semaine / mois / total) s’affichent pour tout le monde, quel que soit le mode. Seule l’action de retrait change.

En mode MERCHANT : remplacer le bouton, ne pas le griser

Un bouton grisé signifie « bloqué par eux » et déclenche un appel au support. Il n’y a rien à débloquer : l’argent n’est jamais passé chez nous. Affichez payout_badge sur le portefeuille, et payout_notice à la place du bouton — avec un lien vers payout_dashboard_url quand il est présent.

Si la demande est tentée quand même, elle est refusée avec un code dédié :

Code Sens
WITHDRAWAL_MERCHANT_ACCOUNT encaissement direct — le retrait se fait chez son agrégateur
INSUFFICIENT_FUNDS mode PLATFORM, mais le solde n’atteint pas le minimum

⚠️ Ne jamais confondre les deux. Un vendeur en encaissement direct a un solde retirable nul chez nous tout en ayant des centaines de ventes réussies : lui répondre « solde insuffisant » lui fait croire que nous retenons son argent.

Le badge n’est pas toujours « FedaPay »

Plusieurs agrégateurs sont pris en charge (FedaPay, Kkiapay, CinetPay, Chario, Flutterwave, MyCoolPay, Wave, GeniusPay). payout_badge est dérivé de l’agrégateur réellement lu sur les ventes du marchand : affichez-le tel quel, ne le déduisez pas côté client.

Deux replis à gérer :

Un vendeur qui a basculé d’un mode à l’autre reste PLATFORM tant qu’il existe des ventes encaissées chez nous : cette part-là lui est due, quoi qu’il encaisse par ailleurs.

9. Ce que cet état ne couvre pas encore

Il ne dit pas si le portail captif est correctement déployé sur le routeur, ni si le walled-garden autorise les hôtes de paiement. Ces deux contrôles exigent d’interroger le routeur, ce qui est incompatible avec un état demandé à chaque ouverture d’écran.

Autrement dit : un READY garantit que la vente et la livraison sont possibles côté plateforme. Il ne garantit pas encore que le client capté par le hotspot atteindra la page de paiement.