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
-
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_subscriptionsest 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_codede la licence etmetadata.plan_codede 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_paiementest sans effet secondaire (pas de double prolongation).
-
Garantie de non-coupure et préservation des ressources :
- Le renouvellement prolonge l’allocation ciblée (
target_allocation_id) viaprolongerEcheance. - 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
EXPIREDouDISABLED(dans le délai avant purge), elle repasse immédiatement àACTIVEet le pair WireGuard / flux Nginx est réactivé.
- Le renouvellement prolonge l’allocation ciblée (
-
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) :
vpn_6months(planvpn-6m, 180 j, 4 000 XOF indicatif)vpn(planvpn, 365 j, 45 000 XOF indicatif)mikhmoai_pro(planmikhmoai-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-planspour n’afficher que les offres réellement disponibles pour ce silo et leur tarif courant.
-
Page multi-opérateurs par défaut :
- Par défaut (aucun
providerenvoyé),renewal-checkoutrenvoie unecheckout_urlpointant 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.
- Par défaut (aucun
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
- Méthode :
GET - URL :
/api/v1/vpn/allocations/:id/renewal-plans - Headers :
Authorization: Bearer <jwt_client_token> Accept: application/json - Paramètre URL :
:id: UUID de la passerelle concernée.
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
- Méthode :
POST - URL :
/api/v1/vpn/allocations/:id/renewal-checkout - Headers :
Authorization: Bearer <jwt_client_token> Content-Type: application/json - Corps de la 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
- Méthode :
GET - URL :
/api/v1/vpn/allocations/:id/renewal-status?ref=<reference_paiement> - Headers :
Authorization: Bearer <jwt_client_token> Accept: application/json - Paramètres URL et Query :
:id: UUID de la passerelle concernée.ref(optionnel) : Référence de paiement retournée lors du checkout (order_id/ référence transaction). Si absente, renvoie le dernier renouvellement enregistré et garantit l’égalité de l’échéance passerelle et licence.
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
- Appeler
GET /api/v1/vpn/allocations/:id/renewal-plansavant d’afficher les boutons de durée. - N’afficher que les choix retournés dans
data(ex. masquer l’option 1 mois si elle n’est pas dans la liste). - Envoyer le
plan_code(alias) lors duPOST /api/v1/vpn/allocations/:id/renewal-checkout. - Ouvrir
checkout_urldans le navigateur ou la WebView pour laisser l’utilisateur choisir son opérateur de paiement. - Poller
GET /api/v1/vpn/allocations/:id/renewal-status?ref=<order_id>dès le retour sur l’application jusqu’à obtenirstatus: "renewed". - Afficher
nouvelle_findès confirmation sans rechargement lourd de page.