Documentation J+SERVICES Guides Référence API

Contrat API — Renouvellement et prolongation des passerelles VPN

Le principe en une phrase : un renouvellement prolonge la passerelle et la licence existantes du client (mêmes ports, mêmes clés WireGuard/SSTP, report exact des jours restants, mise à jour in-place de la licence sans création de doublon) en offrant le choix multi-opérateurs et les durées réelles du catalogue du silo.

Ce document décrit le contrat d’interface pour le renouvellement et la prolongation des passerelles VPN côté PWA, application mobile et intégrateurs.


1. Règles Métier Fondamentales

  1. Le renouvellement prolonge la licence existante, ne crée pas de licence (règle propriétaire 2026-10-28) :

    • Une passerelle VPN est toujours attribuée par UNE licence.
    • Renouveler / prolonger / basculer vers une autre offre = mettre à jour cette licence existante in-place.
    • JAMAIS de nouvelle ligne dans la table licenses, jamais de nouvelle allocation créée.
    • Si un abonnement client_product_subscriptions est associé, il est également mis à jour (même échéance, même plan) sans créer de doublon.
    • Nouvelle échéance = prolongerEcheance(fin actuelle, maintenant, durée) (si fin future : fin + durée ; sinon : maintenant + durée).
    • Statut de la licence et de la passerelle → ACTIVE.
    • plan_code de la licence et metadata.plan_code de la passerelle → plan choisi.
    • Historique tracé de manière exhaustive dans licenses.metadata.renouvellements[] : { at, ancien_plan, nouveau_plan, jours, ancienne_fin, nouvelle_fin, montant, devise, prestataire, reference_paiement }.
    • Idempotence absolue : tout webhook rejoué ou passage de réconciliateur portant la même reference_paiement est sans effet secondaire (pas de double prolongation).
  2. Garantie de non-coupure et préservation des ressources :

    • Le renouvellement prolonge l’allocation ciblée (target_allocation_id) via prolongerEcheance.
    • Les ports attribués (webfig_port, winbox_port, ssh_port, api_port), l’IP tunnel (router_tunnel_ip), les clés WireGuard et les identifiants SSTP restent strictement identiques.
    • Le routeur MikroTik physique sur site ne subit aucune déconnexion ni reconfiguration.
    • Si la passerelle était EXPIRED ou DISABLED (dans le délai avant purge), elle repasse immédiatement à ACTIVE et le pair WireGuard / flux Nginx est réactivé.
  3. Catalogue d’offres par silo (liste fixe) :

    • Le renouvellement propose une LISTE FIXE d’offres par silo, quelle que soit la licence actuelle du client (Promo, Mikhmon Pro, VPN) : il renouvelle ou passe à l’une de ces offres et conserve sa passerelle.
    • Silo maître (défaut) :
      1. vpn_6months (plan vpn-6m, 180 j, 4 000 XOF indicatif)
      2. vpn (plan vpn, 365 j, 45 000 XOF indicatif)
      3. mikhmoai_pro (plan mikhmoai-pro, 365 j, 17 400 XOF indicatif — alias acceptés : MIKHMOAI_PRO, mikhmoai-pro, pro)
    • Pas de J+RADIUS ni Starlink dans les offres de renouvellement.
    • Silo partenaire : surchargeable via la variable d’environnement VPN_RENEWAL_OFFERS (JSON, ex: vpn_1month→vpn-mensuel, vpn_6months→vpn-6mois, vpn→vpn-annuel).
    • ⚠️ Montants indicatifs et dynamiques : les montants affichés dans ce document sont fournis à titre d’exemple. Les montants réels sont toujours lus dynamiquement depuis l’API via la route GET /api/v1/vpn/allocations/:id/renewal-plans. Le frontend ne doit jamais coder les montants en dur.
    • Le frontend doit interroger GET /api/v1/vpn/allocations/:id/renewal-plans pour n’afficher que les offres réellement disponibles pour ce silo et leur tarif courant.
  4. Page multi-opérateurs par défaut :

    • Par défaut (aucun provider envoyé), renewal-checkout renvoie une checkout_url pointant vers le Hub de paiement multi-opérateurs (/api/v1/payments/hub).
    • L’acheteur peut librement choisir son opérateur (FedaPay, SasPay / LICENCE PAY, Cartes bancaires).
    • Les boutons non compatibles avec la prolongation ciblée (ex. lien statique Chariow sans métadonnée cible) sont automatiquement masqués sur le Hub pour éviter de vendre des licences orphelines.

2. Endpoints

2.1. Obtenir les formules disponibles : GET /api/v1/vpn/allocations/:id/renewal-plans

Retourne la liste des formules de renouvellement VPN actives du silo courant pour cette passerelle, triées par durée croissante.

Requête

Réponse de succès (200 OK) — Exemple Silo Maître

{
  "success": true,
  "data": [
    {
      "plan_code": "vpn_6months",
      "alias": "vpn_6months",
      "name": "MikhmoAI VPN 6 Mois",
      "duration_days": 180,
      "amount": 4000,
      "currency": "XOF"
    },
    {
      "plan_code": "vpn",
      "alias": "vpn",
      "name": "MikhmoAI VPN 1 An",
      "duration_days": 365,
      "amount": 45000,
      "currency": "XOF"
    },
    {
      "plan_code": "mikhmoai_pro",
      "alias": "mikhmoai_pro",
      "name": "MikhmoAI Pro 1 An",
      "duration_days": 365,
      "amount": 17400,
      "currency": "XOF"
    }
  ]
}

Réponse de succès (200 OK) — Exemple Silo GWD (avec 1 mois)

{
  "success": true,
  "data": [
    {
      "plan_code": "vpn_1month",
      "alias": "vpn_1month",
      "name": "MikhmoAI VPN 1 Mois",
      "duration_days": 30,
      "amount": 5000,
      "currency": "XOF"
    },
    {
      "plan_code": "vpn_6months",
      "alias": "vpn_6months",
      "name": "MikhmoAI VPN 6 Mois",
      "duration_days": 180,
      "amount": 4000,
      "currency": "XOF"
    },
    {
      "plan_code": "vpn",
      "alias": "vpn",
      "name": "MikhmoAI VPN 1 An",
      "duration_days": 365,
      "amount": 45000,
      "currency": "XOF"
    }
  ]
}

2.2. Initier le paiement de renouvellement : POST /api/v1/vpn/allocations/:id/renewal-checkout

Prépare la session de renouvellement pour la passerelle désignée.

Requête

{
  "plan_code": "vpn_6months"
}

Note : provider est optionnel. Si omis, l’URL retournée charge le Hub multi-opérateurs. Si renseigné (fedapay, saspay), un checkout direct avec ce prestataire est initié pour compatibilité application mobile.

Codes de plan et correspondances

plan_code envoyé Durée Comportement
vpn_1month +30 jours Mappe sur le plan VPN mensuel configuré dans le silo (VPN_RENEWAL_OFFERS). Renvoie 400 INVALID_PLAN_CODE si le silo ne propose pas d’offre mensuelle.
vpn_6months (ou vpn-6m) +180 jours Mappe sur le plan 6 mois (vpn-6m).
vpn (ou vpn_1year_standard) +365 jours Mappe sur le plan annuel standard (vpn).
mikhmoai_pro (ou MIKHMOAI_PRO, mikhmoai-pro, pro) +365 jours Mappe sur le plan annuel MikhmoAI Pro (mikhmoai-pro, 17 400 XOF).
(absent ou omis) +365 jours Standard 1 an (plan vpn). Un client Promo renouvelle vers l’offre standard.

Réponse de succès (200 OK)

{
  "success": true,
  "checkout_url": "https://live.jmoai.net/api/v1/payments/hub?productCode=mikhmoai&planCode=VPN-6M&returnUrl=mikhmoai%3A%2F%2Fbilling%2Fsuccess&email=client%40example.com&targetAllocationId=11111111-2222-4333-8444-555555555555&target_allocation_id=11111111-2222-4333-8444-555555555555",
  "order_id": "LIC_RNW_1793193200123_abc456",
  "allocation_id": "11111111-2222-4333-8444-555555555555",
  "plan": {
    "code": "vpn_6months",
    "name": "MikhmoAI VPN 6 Mois",
    "duration_days": 180,
    "amount": 4000,
    "currency": "XOF"
  },
  "expires_in_minutes": 60,
  "data": {
    "checkout_url": "https://live.jmoai.net/api/v1/payments/hub?...",
    "provider": "hub",
    "reference": "LIC_RNW_1793193200123_abc456",
    "order_id": "LIC_RNW_1793193200123_abc456",
    "amount": 4000,
    "currency": "XOF",
    "plan_code": "VPN-6M",
    "plan": {
      "code": "vpn_6months",
      "name": "MikhmoAI VPN 6 Mois",
      "duration_days": 180,
      "amount": 4000,
      "currency": "XOF"
    },
    "allocation_id": "11111111-2222-4333-8444-555555555555",
    "expires_in_minutes": 60,
    "validity_after_payment": {
      "starts_at": "2026-11-02T12:00:00.000Z",
      "ends_at": "2027-05-01T12:00:00.000Z"
    }
  }
}

2.3. Consulter le statut après paiement : GET /api/v1/vpn/allocations/:id/renewal-status

Permet au client (PWA ou mobile) de vérifier si le renouvellement a été pris en compte suite au paiement et d’obtenir immédiatement la nouvelle date d’échéance.

Requête

Réponse de succès (200 OK) — Renouvellement validé (status: "renewed")

{
  "success": true,
  "data": {
    "status": "renewed",
    "plan": "vpn-6m",
    "ancienne_fin": "2026-11-02T12:00:00.000Z",
    "nouvelle_fin": "2027-05-01T12:00:00.000Z",
    "jours_ajoutes": 180,
    "license_id": "11111111-2222-3333-4444-555555555555"
  }
}

Réponse de succès (200 OK) — En attente (status: "pending")

Lorsque le paiement est en cours de traitement ou que le webhook n’a pas encore été reçu :

{
  "success": true,
  "data": {
    "status": "pending",
    "plan": "vpn-6m",
    "ancienne_fin": "2026-11-02T12:00:00.000Z",
    "nouvelle_fin": null,
    "jours_ajoutes": 0,
    "license_id": "11111111-2222-3333-4444-555555555555"
  }
}

Réponse de succès (200 OK) — Paiement échoué (status: "failed")

Lorsque le prestataire a notifié un échec ou une annulation du paiement :

{
  "success": true,
  "data": {
    "status": "failed",
    "plan": "vpn-6m",
    "ancienne_fin": "2026-11-02T12:00:00.000Z",
    "nouvelle_fin": null,
    "jours_ajoutes": 0,
    "license_id": "11111111-2222-3333-4444-555555555555"
  }
}

3. Parcours Applicatif Recommandé

Le flux nominal d’un renouvellement côté client s’articule comme suit :

[1. plans]          GET /api/v1/vpn/allocations/:id/renewal-plans
                       │  (Affiche uniquement les durées disponibles et tarifs réels)
                       ▼
[2. checkout]       POST /api/v1/vpn/allocations/:id/renewal-checkout { plan_code }
                       │  (Reçoit checkout_url du Hub + order_id)
                       ▼
[3. paiement]       Ouverture de checkout_url (Hub multi-opérateurs)
                       │  (Le client finalise son règlement via FedaPay, SasPay, etc.)
                       ▼
[4. polling]        GET /api/v1/vpn/allocations/:id/renewal-status?ref=<order_id>
                       │  (Boucle toutes les 2-3s jusqu'à status === "renewed")
                       ▼
[5. affichage]      Mise à jour immédiate de l'écran avec nouvelle_fin

4. Codes d’Erreur

Code HTTP code Description / Cause
400 INVALID_PLAN_CODE La durée demandée n’existe pas dans le catalogue du silo courant (ex. demande de vpn_1month chez le maître). Le champ message liste les choix réellement autorisés.
400 PLAN_NOT_ALLOWED Le plan demandé n’est pas un plan VPN payant (ex. FREE, TRIAL, PROMO, TIKETMOMO) ou est inconnu.
401 UNAUTHENTICATED Jeton d’authentification absent ou invalide.
404 ALLOCATION_NOT_FOUND La passerelle n’existe pas, n’est pas un UUID valide, ou appartient à un autre compte.
409 ALLOCATION_PURGED La passerelle a dépassé le délai de grâce de 7 jours et a été purgée. Elle ne peut plus être renouvelée ; un nouvel accès doit être commandé.

Exemple d’erreur 400 INVALID_PLAN_CODE

{
  "success": false,
  "code": "INVALID_PLAN_CODE",
  "message": "Cette durée n'est pas proposée. Choisissez parmi : vpn, vpn_6months",
  "available_plans": ["vpn", "vpn_6months"]
}

5. Ce que le Frontend PWA doit implémenter

  1. Appeler GET /api/v1/vpn/allocations/:id/renewal-plans avant d’afficher les boutons de durée.
  2. N’afficher que les choix retournés dans data (ex. masquer l’option 1 mois si elle n’est pas dans la liste).
  3. Envoyer le plan_code (alias) lors du POST /api/v1/vpn/allocations/:id/renewal-checkout.
  4. Ouvrir checkout_url dans le navigateur ou la WebView pour laisser l’utilisateur choisir son opérateur de paiement.
  5. Poller GET /api/v1/vpn/allocations/:id/renewal-status?ref=<order_id> dès le retour sur l’application jusqu’à obtenir status: "renewed".
  6. Afficher nouvelle_fin dès confirmation sans rechargement lourd de page.