Documentation J+SERVICES Guides Référence API

Contrat API — Capturer l’e-mail avant le paiement (checkout invité)

Le principe en une phrase : un achat sans adresse exploitable produit un client injoignable — on encaisse, et on n’a aucun moyen de livrer.

Ce guide s’adresse aux développeurs de la boutique, du hub et de l’app mobile. Il décrit un seul changement : demander l’e-mail au moment du clic sur le moyen de paiement, avant de créer la session.


1. Le problème, mesuré

Sur les 10 ventes réelles encaissées chez GeniusPay depuis juillet 2026 :

Type d’achat Ventes Livrées
avec une vraie adresse 6 5
en invité (adresse fabriquée) 4 0

Les quatre achats invité ont tous échoué. Aucune exception. Ce n’est ni le prestataire de paiement ni le webhook : les deux fonctionnent. C’est l’adresse.

Sur 30 jours, 92 tentatives de licence sur 260 partent en invité — un tiers du volume.

Pourquoi une adresse fabriquée ne marche pas

Quand aucune adresse n’est fournie, le backend en génère une pour que l’appel au prestataire passe :

guest_1786931775582_5a1eb2cd@guest.jmoai.net

Le domaine guest.jmoai.net n’a aucun enregistrement MX. Rien n’y arrive — ni chez le client, ni chez nous. Le reçu du prestataire part dans le vide, et notre propre code rejette ensuite cette adresse comme inutilisable, à juste titre. Résultat : paiement encaissé, aucune licence créée, acheteur introuvable.


2. Ce qu’il faut faire côté front

Quand l’utilisateur n’a pas de session et clique sur un moyen de paiement (FedaPay, GeniusPay, ou tout autre), afficher un champ e-mail et exiger sa saisie avant d’appeler le backend.

C’est le seul moment où la capture est possible : une fois sur la page du prestataire, nous ne contrôlons plus rien — ni le formulaire, ni la redirection de retour.

L’appel

POST /api/v1/clients/checkout-license
{
  "email": "client@exemple.com",   // ⬅️ obligatoire sans session
  "productCode": "mikhmoai",
  "planCode": "mikhmoai-pro",
  "provider": "geniuspay"
}

Le champ est email. Avec une session valide, il est facultatif — l’adresse du compte est utilisée.

Le refus à gérer

HTTP 400
{
  "success": false,
  "code": "BUYER_EMAIL_REQUIRED",
  "error": "Une adresse e-mail est requise pour recevoir votre licence."
}

Affichez le champ e-mail et laissez l’utilisateur reprendre. Ce n’est pas une erreur technique : ne montrez ni « erreur serveur », ni code brut.


2 bis. Les deux formes de réponse — lisez strategy

Le même appel répond de deux manières selon que le plan est vendu ou non sur la boutique internationale Chario. Dans les deux cas, ouvrez checkout_url et gardez internal_tx_id pour interroger GET /api/v1/payments/status/{id}.

// Plan vendu SUR PLUSIEURS boutiques (mikhmoai-pro, starlink-x-mikrotik…)
{ "strategy": "HUB_WIDGET", "provider": "chariow-widget",
  "checkout_url": "https://live.jmoai.net/api/v1/payments/hub?..." }

// Plan vendu chez nous seulement
{ "strategy": "LOCAL_ONLY", "provider": "fedapay",
  "checkout_url": "<page hébergée du prestataire>" }

Sans provider dans la requête (rétabli le 2026-10-19) — app mobile, PWA :

{ "is_hub": true, "strategy": "PAYMENT_HUB",
  "checkout_url": "https://live.jmoai.net/api/v1/payments/hub?productCode=...&planCode=...",
  "target_allocation_id": "<uuid, seulement pour un renouvellement ciblé>" }

Aucune transaction n’est créée à cette étape : l’acheteur choisit son moyen de paiement sur le hub, qui crée alors la transaction. L’internal_tx_id de cette réponse n’est donc pas encore interrogeable sur /payments/status (404) : c’est la transaction créée par le hub qui fait foi. Avec un provider explicite, la réponse est l’une des deux formes ci-dessus.

HUB_WIDGET renvoie vers notre page de sélection, qui présente les boutiques réellement disponibles pour ce plan et leurs prix. Le provider que vous avez envoyé n’est alors pas consommé : c’est l’utilisateur qui choisit sur cette page.

⚠️ Le nom chariow-widget est historique : il ne veut pas dire qu’un widget est embarqué. Chaque boutique de la page est un lien plein écran vers la page de paiement hébergée du prestataire. Ouvrez simplement checkout_url, rien n’est à intégrer côté application.

⚠️ Le prix Chario est volontairement différent du nôtre — Chario prélève sa commission, sa vitrine est donc au-dessus de la locale. Pour STARLINK X au 30/08/2026 : 16 300 XOF chez nous, 17 300 XOF chez Chario. N’affichez jamais notre prix au-dessus d’un bouton Chario : le montant débité serait plus élevé que celui annoncé. Le hub, lui, affiche déjà le bon prix en face de chaque boutique.

Une boutique absente de la page n’est pas une panne : elle signifie que ce plan n’y est pas publié, ou que la clé de l’agrégateur n’est pas configurée. GET /api/v1/payments/providers dit lesquels sont réellement ACTIVE.


3. Ce que le backend garantit une fois l’adresse fournie

Sans adresse, rien de tout cela n’est possible : ce n’est pas un choix de conception, c’est qu’il n’existe aucun moyen de joindre l’acheteur.


4. Une adresse suffit — la page de retour n’est pas la solution

Une page de livraison affichée après paiement ne peut pas remplacer la capture de l’adresse, pour deux raisons :

  1. Nous ne contrôlons pas le retour du prestataire. Une fois chez GeniusPay, la redirection n’est ni garantie ni maîtrisable.
  2. Le client ferme souvent l’onglet. Une clé affichée une seule fois, sans support écrit, est une clé perdue.

Une page de confirmation reste utile — elle rassure et évite les paiements répétés — mais elle vient en plus de l’e-mail, jamais à sa place.


5. Bascule progressive — ne pas casser les ventes

Le refus est piloté par une variable serveur :

Mode Comportement
observe (actuel) l’achat passe, l’adresse est fabriquée, un avertissement est journalisé
enforce l’achat sans adresse est refusé avec BUYER_EMAIL_REQUIRED

Le mode enforce ne sera activé qu’une fois les trois fronts à jour. L’activer avant refuserait un tiers des achats.

Ordre recommandé :

  1. Les fronts implémentent le champ et gèrent BUYER_EMAIL_REQUIRED.
  2. On vérifie que la part d’achats invité tombe à zéro dans les journaux.
  3. On bascule en enforce — le filet se referme définitivement.

Tant que la première étape n’est pas faite, chaque achat invité est une vente encaissée sans contrepartie.


6. Voir aussi