Documentation J+SERVICES Guides Référence API

Contrat API — Points de vente TiketMOMO (une boutique, plusieurs routeurs)

Public : développeurs de l’app mobile et des portails captifs. Toutes les valeurs ci-dessous sont fictives.

1. Le problème réglé

Une boutique (store_slug) identifie le vendeur, pas l’équipement. Un vendeur peut vendre sur plusieurs routeurs, et le nom du serveur hotspot (nasid) n’est pas unique : deux routeurs nommés « WIFI-ZONE » et « WIFI ZONE », ou le même « hotspot1 » chez plusieurs vendeurs.

Conséquences sans point de vente : un ticket tiré du stock du mauvais routeur (code invalide pour l’acheteur), des ventes impossibles à séparer par routeur.

Un point de vente = un routeur = un créneau VPN. Son identifiant, point_id, est stable tant que le routeur garde son créneau. C’est lui qui porte le stock de tickets de ce routeur.

2. Lister les points de vente

GET /api/v1/clients/store/points — jeton client.

{
  "success": true,
  "data": {
    "currency": "XOF",
    "points": [
      {
        "point_id": "11111111-1111-4111-8111-111111111111",
        "slot": 42,
        "nas_id": "WIFI-ZONE",
        "store_slug": "ma-boutique",
        "router": { "identity": "Salle A", "serial": "ABC123XYZ", "model": "RB951Ui-2HnD" },
        "tunnel": {
          "status": "CONNECTED",
          "transport": "SSTP",
          "last_seen_at": "2026-09-14T10:00:00Z"
        },
        "purchase_link": "https://tpay.mikhmoai.com/api/v1/store/buy-ticketmomo?store=ma-boutique&point=11111111-1111-4111-8111-111111111111&nasid=$(server-name)&mac=$(mac)&ip=$(ip)&link-status=$(link-status-esc)",
        "stock": { "total": 180, "by_profile": { "100F-5H": 30, "250F-1d": 30 } },
        "sales": {
          "today": { "revenue": 450, "count": 3 },
          "this_week": { "revenue": 1200, "count": 8 },
          "this_month": { "revenue": 5400, "count": 36 }
        }
      }
    ],
    "unattributed_sales": {
      "today": { "revenue": 0, "count": 0 },
      "this_week": { "revenue": 0, "count": 0 },
      "this_month": { "revenue": 700, "count": 5 }
    }
  }
}
Champ Sens
points[] Un élément par routeur ACTIF du vendeur, trié par créneau.
store_slug Boutique qui vend pour ce routeur : celle de sa licence, sinon la boutique principale (la plus ancienne). Deux routeurs peuvent partager la même boutique.
purchase_link Lien d’achat prêt à poser dans le portail de CE routeur. Les $(…) sont des variables MikroTik : ne pas les encoder ni les remplacer.
stock.by_profile Tickets disponibles dans le stock de CE routeur, par profil hotspot.
sales Ventes payées attribuées à CE routeur. Fenêtres en heure d’Afrique de l’Ouest (UTC+1), semaine à partir du lundi.
unattributed_sales Ventes du mois qu’aucune preuve ne rattache à un routeur actif (routeur retiré, vente ancienne). Afficher à part, ne pas les répartir.

3. Tableau de bord d’un point de vente

GET /api/v1/clients/store/points/:pointId — jeton client.

Même objet qu’un élément de points[], plus currency et recent_sales (50 dernières ventes du mois pour ce routeur, les plus récentes d’abord) :

"recent_sales": [
  { "id": "TM_0000000000", "amount": 250, "currency": "XOF", "profile": "250F-1d", "created_at": "2026-09-14T09:47:18Z" }
]
Code HTTP error.code Quand
400 POINT_ID_INVALID pointId n’est pas un UUID.
404 POINT_NOT_FOUND Routeur inactif, inexistant, ou appartenant à un autre client (on ne confirme pas son existence).

4. Le lien d’achat : point

Base : https://tpay.mikhmoai.com/api/v1/store/buy-ticketmomo — la racine https://tpay.mikhmoai.com/buy-ticketmomo répond 404.

GET /api/v1/store/buy-ticketmomo?nasid=$(server-name)&amount=100&currency=cfa&profile_name=100F-5H&mac=$(mac)&ip=$(ip)&link-status=$(link-status-esc)&store=<slug>&point=<point_id>[&pub_key=pk_live_…]

Si amount ou profile_name manque et que store, client_id ou le suffixe -TM<n> de nasid permet d’identifier le vendeur, ce même lien affiche son catalogue de forfaits livrables. Chaque forfait ouvre ensuite le lien complet d’achat, avec les paramètres réseau effectivement reçus. Les macros MikroTik encore sous la forme $(...) ne sont jamais recopiées ; dans ce cas, la page invite l’acheteur à rouvrir le lien depuis le WiFi du vendeur.

Qui est le vendeur ? (ordre de priorité)

  1. store (slug de boutique) ;
  2. sinon client_id ;
  3. sinon pub_key (vendeur retrouvé par sa clé publique FedaPay enregistrée) ;
  4. sinon nasid seul, en dernier recours (nom non unique : à éviter).

store et client_id peuvent coexister : store l’emporte.

Révision 2026-09-16 — gabarit MikroTik : chaque portail généré pour une allocation ajoute désormais point=<allocation_id> au lien d’achat et transmet nasid=$(server-name). Il ne transmet jamais $(identity), qui désigne le routeur et non le serveur Hotspot. Ainsi, même si un NAS est renommé, la vente reste épinglée au bon point de vente.

Les anciens portails qui envoient encore $(identity) restent pris en charge uniquement à l’intérieur du vendeur déjà résolu et seulement si cette identité désigne une allocation unique. Une identité partagée reste refusée : le serveur ne normalise jamais les espaces, tirets ou suffixes pour « deviner » un routeur.

Quel routeur ? point

pub_key — qui encaisse

Montant, durée

5. Webhooks et transactions

À l’ouverture du paiement, l’allocation retenue est écrite dans transactions.metadata.canonical_allocation_id : c’est le point_id, et il suit la transaction jusqu’au webhook de paiement puis à la livraison du ticket.

Attribution d’une vente à un point, par force de preuve :

  1. le stock d’où vient le ticket livré (vouchers.site_id = tiketmomo_<point_id>) ;
  2. transactions.allocation_id ;
  3. transactions.metadata.canonical_allocation_id.

Jamais nas_id.

6. Pousser des tickets depuis l’app

POST /api/v1/vouchers/sync range les tickets dans le stock du routeur désigné par metadata.allocationId (= point_id), metadata.routerSerial ou metadata.nasId.

7. À ne pas faire

8. Identité NAS gérée

Le service NasIdentityAllocationService attribue à chaque allocation routeur un NAS stable au format TiketMOMO TM-1, TM-2, TM-3, etc. L’allocation est réservée dans nas_identity_registry avant toute écriture distante ; le même identifiant est réutilisé lors des reprises et n’est jamais réattribué à une autre allocation.

L’écriture sur MikroTik est idempotente et vérifiée : REST sur RouterOS 7, SSH borné sur RouterOS 6. Le service n’écrit que si un seul serveur Hotspot est identifié ; plusieurs serveurs, routeur non joignable ou conflit de réservation donnent un état d’échec explicite. Le portail doit ensuite envoyer nasid=$(server-name) et point=<point_id>.

Quatre gardes empêchent les doublons : clé primaire sur nas_id, unicité de l’allocation, unicité du compteur séquentiel et relecture idempotente avant insertion (avec gestion des courses concurrentes). Une collision est refusée et ne change jamais de routeur.