Ajouter un service SaaS (exemple avec sci-facturation)

Cette page déroule de bout en bout l'ajout d'un webservice appelant. Elle prend sci-facturation pour exemple : remplacez son nom, son préfixe et l'URL de son webhook par ceux de votre propre service. Comptez une quinzaine de minutes, hors saisie du catalogue.

Les étapes se suivent dans l'ordre de leurs dépendances : chacune a besoin de la précédente.

Étape 1 : créer l'utilisateur de service côté Dolibarr

Dans Accueil > Utilisateurs et groupes > Nouvel utilisateur, créez un utilisateur dédié, par exemple svc-scifacturation, avec :

  • un mot de passe aléatoire que personne n'utilise, car cet utilisateur ne se connecte jamais à l'interface ;
  • les droits sur les tiers (lecture et création), sur les produits et services, sur les commandes et sur les factures. C'est en effet lui qui porte les écritures comptables produites par l'API.

Pourquoi un utilisateur dédié ? D'abord parce qu'un compte administrateur ne suffit pas : Dolibarr vérifie les droits bruts, module par module, au moment de valider une commande ou une facture. Ensuite parce qu'un utilisateur partagé entre plusieurs services rendrait le journal comptable illisible.

Fiche de l'utilisateur de service avec les droits tiers, produits, commandes et factures cochés

Étape 2 : créer le client OAuth chez smartauth

Ouvrez la liste des clients OAuth par Accueil > Configuration > Modules > smartauth (icône d'engrenage), onglet OAuth, puis Nouveau client OAuth.

Formulaire de création d'un client OAuth dans smartauth

Renseignez le formulaire ainsi :

Champ Valeur Pourquoi
Nom SCIfacturation M2M Un libellé libre, destiné à vous seul
Type Confidentiel Obligatoire pour le flux machine à machine (M2M)
PKCE obligatoire Non PKCE protège le flux à code d'autorisation et ne s'applique pas au flux M2M
Grants autorisés client_credentials seul Un seul webservice, donc un seul moyen de s'authentifier
Redirect URIs https://scifacturation.example/callback Jamais utilisé par ce flux ; le champ accepte une valeur de remplissage
Utilisateur de service svc-scifacturation L'utilisateur créé à l'étape 1
Durée access token 3600 (1 heure) Le webservice met le jeton en cache et en redemande un à l'expiration
Durée refresh token Valeur par défaut Sans importance : ce flux n'émet jamais de refresh token
Scopes Rien à cocher Le périmètre du service est borné par sa fiche dans smartmakersaasbilling, et non par des scopes
Statut Actif

À l'enregistrement, l'écran affiche une seule fois le client_id et le secret en clair : copiez-les immédiatement. Le secret n'est conservé que sous forme hachée, il ne pourra donc plus être relu, seulement régénéré.

Puis, sur cette même fiche, cliquez sur "Autoriser sur l'API interne". Les routes que les modules exposent en oauth2 sont fermées par défaut ; sans cela, n'importe quel client OAuth de l'instance pourrait appeler n'importe quelle route de n'importe quel module avec les droits de son utilisateur de service.

C'est l'étape la plus facile à oublier, parce que son absence ne ressemble pas à un oubli : le webservice obtient son jeton sans difficulté, la route est bien trouvée, et l'appel répond malgré tout 401. Seul le journal Dolibarr le dit, sous la forme SMARTAUTH_API_AUDIENCE not set - refusing <client_id>. La fiche du produit autorisé, à l'étape suivante, signale le cas et renvoie ici.

Étape 3 : créer la fiche produit autorisé

Revenez dans smartmakersaasbilling, onglet Produits SaaS autorisés, puis Nouveau produit autorisé :

Champ Valeur
Réf. scifacturation, stable et jamais renommée par la suite
Libellé Celui de votre choix
Client OAuth smartauth Le client créé à l'étape 2
Préfixe de références produit P-SCIFAC-
URL du webhook de règlement https://<votre-webservice>/api/v1/webhooks/dolibarr
Actif Oui

Fiche de création du produit SaaS autorisé dans smartmakersaasbilling

Le secret de signature du webhook est généré à l'enregistrement, puis affiché sur la fiche : transmettez-le au webservice par un canal sûr. C'est avec lui qu'il vérifiera la signature des notifications de règlement.

Tant qu'aucune fiche active n'existe, tout appel à l'API est rejeté : l'API refuse par défaut, ce qui rend cette étape indispensable.

Étape 4 : créer le catalogue

Le service a besoin d'une fiche produit par acte facturable. Pour sci-facturation, cela donne :

  • P-SCIFAC-ABO-T1 à P-SCIFAC-ABO-T4 pour les abonnements annuels par tranche ;
  • P-SCIFAC-MES-T1 à P-SCIFAC-MES-T4 pour les mises en service au prorata ;
  • P-SCIFAC-MUT pour le forfait de mutation.

Seul le préfixe est configuré, à l'étape 3 ; le suffixe encode la nature de l'acte et la tranche, et c'est le webservice qui le déduit. Ne recopiez pas ces références à la main : un caractère erroné passe inaperçu jusqu'au premier acte à commander, où la commande est refusée faute de produit correspondant.

Demandez plutôt au webservice son gabarit de catalogue, un fichier JSON qui énumère les fiches qu'il attend. L'annexe en décrit le format ; du côté de sci-facturation, la commande est php artisan sci:billing-catalogue-template. Sur la fiche du produit autorisé, le bouton Importer un gabarit de catalogue affiche alors, ligne par ligne, ce qu'il créerait, avec le prix proposé par le webservice, que vous pouvez modifier avant création.

Trois règles gouvernent cet écran :

  • une fiche déjà présente n'est jamais modifiée : le prix relève d'une décision commerciale prise ici, et un gabarit capable de l'écraser ferait du fichier la source du barème. Un nouvel import ne peut donc rien détériorer ;
  • seules les références portant le préfixe du client sont reprises ;
  • rien ne transite par l'API : le fichier se dépose à la main, si bien qu'un jeton compromis reste incapable de créer la moindre fiche produit.

Les fiches créées sont de type service et actives à la vente. Vérifiez les prix avant de valider : un service sans prix est refusé à la facturation, délibérément, et le webservice le signale également de son côté.

Un service qui ne produit pas de gabarit se saisit à la main, dans Produits et services, avec les mêmes caractéristiques.

Étape 5 : vérifier la configuration de l'instance

Ouvrez l'onglet Configuration du module : il signale de lui-même ce qui manque, à savoir

  • les constantes PAYMENT_SECURITY_TOKEN et PAYMENT_SECURITY_TOKEN_UNIQUE du cœur de Dolibarr. Sans elles, le lien de paiement est construit sans sa clé de sécurité et la page de paiement refuse la commande ;
  • l'absence de module de paiement en ligne, qu'il s'agisse de stripe ou de stancer.

Écran de configuration du module avec les avertissements de constantes manquantes

Étape 6 : remettre les informations au webservice

Le service appelant reçoit quatre informations, et pas une de plus :

  1. le client_id et le secret OAuth de l'étape 2 ;
  2. le secret de signature du webhook de l'étape 3 ;
  3. l'adresse de l'API : https://<votre-dolibarr>/custom/smartmakersaasbilling/pwa/api.php ;
  4. l'adresse du endpoint de jeton, qui ne se trouve pas sous cet hôte : https://<votre-portail-sso>/oauth/token.

Ces deux dernières adresses constituent le second piège du parcours. Le portail SSO de smartauth est exposé sur un sous-domaine dédié, dont la racine web est son propre répertoire public/, et il ne reconnaît un endpoint OAuth que sur un chemin absolu commençant par /oauth/. Toute adresse de la forme https://<votre-dolibarr>/custom/smartauth/... répond 404, en page HTML, sans autre explication. La valeur exacte se lit sur l'instance :

curl -s https://<votre-portail-sso>/.well-known/openid-configuration | jq .token_endpoint

Deux secrets circulent donc, et ils sont différents : celui de l'étape 2 sert à obtenir un jeton, celui de l'étape 3 à vérifier la signature des notifications de règlement. Les confondre côté webservice fait rejeter chaque règlement en 401 ; l'accès du client ne se rouvre alors qu'au prochain rapprochement, au lieu de la minute qui suit son paiement.

Le webservice obtient ses jetons contre le couple client_id/secret, met chaque jeton en cache pour sa durée de vie diminuée d'une marge, et vérifie la signature des notifications avec le secret de webhook.

Vérifier que tout fonctionne

Le test le plus simple consiste à lire le catalogue. Avant la création de la fiche de l'étape 3, l'appel est refusé ; après, il renvoie les fiches produit du préfixe du service ainsi que la précision décimale des prix. Le webservice peut également dérouler son propre écran de vérification de catalogue sur ce même appel.

En cas de refus

  • Tout appel refusé (403) : aucune fiche active ne lie ce client OAuth. Revenez à l'étape 3, ou vérifiez que la fiche est bien active.
  • Refus d'authentification (401) : le couple client_id/secret est erroné, ou le client OAuth est inactif, ou il n'est pas confidentiel.
  • 401 alors que le jeton a bien été délivré : le client n'est pas autorisé sur l'API interne. C'est le cas le plus déroutant, puisque tout fonctionne jusqu'au dernier moment. Le journal Dolibarr porte alors SMARTAUTH_API_AUDIENCE not set ou not in SMARTAUTH_API_AUDIENCE allow-list. Retournez sur la fiche du client OAuth (étape 2) et cliquez sur Autoriser sur l'API interne.
  • 404 en page HTML sur la demande de jeton : l'adresse du endpoint de jeton pointe sous l'hôte Dolibarr au lieu du portail SSO (étape 6).
  • Chaque règlement rejeté en 401 côté webservice : celui-ci vérifie la signature avec le secret OAuth au lieu du secret de webhook (étape 6).
  • Erreur de droits à la validation d'une commande : l'utilisateur de service ne dispose pas des droits sur les tiers, les produits, les commandes et les factures. Revenez à l'étape 1.
  • Commande refusée faute de produit correspondant : une référence a été recopiée de travers, ou la fiche n'a jamais été créée. L'import de gabarit de l'étape 4 répond précisément à ce problème, et le webservice sait indiquer ce qui manque.

Annexe : le format du gabarit de catalogue

Cette annexe s'adresse à qui développe le webservice appelant : elle décrit le fichier qu'importe l'étape 4, et qu'il vous revient de produire.

{
    "format": "smartmakersaasbilling.catalogue",
    "version": 1,
    "prefix": "P-SCIFAC-",
    "products": [
        {
            "ref": "P-SCIFAC-ABO-T1",
            "label": "Abonnement annuel, 1re location",
            "unit": "an",
            "type": "service",
            "vat_rate": "20",
            "suggested_price_ht": "24.90",
            "price_source": "default"
        }
    ]
}
Champ Obligatoire Ce qu'il contient
format oui Exactement smartmakersaasbilling.catalogue, faute de quoi le fichier est refusé
version oui 1 à ce jour. Une version supérieure est refusée, car un fichier plus récent pourrait porter des champs que ce module laisserait tomber en silence
prefix non Comparé au préfixe du produit autorisé ; s'il en diffère, rien n'est importé
products[].ref oui La référence complète. Si elle sort du préfixe du client, la ligne est écartée et l'écran le signale. Lettres, chiffres, tirets, tirets bas et points
products[].label non Le libellé de la fiche. À défaut, la référence en tient lieu
products[].suggested_price_ht non Une proposition, affichée et modifiable avant création. En son absence, la ligne demeure et le prix est créé à zéro
products[].vat_rate non Le taux appliqué à la création. En son absence, zéro
products[].unit non Ignoré à la création, et ce délibérément : Dolibarr note les durées dans sa propre syntaxe (1y), qui vous reviendrait ensuite comme unité d'affichage. Conservez votre libellé de votre côté
products[].type non Toutes les fiches sont créées en service
products[].price_source non Informatif

Deux limites encadrent l'import : 100 lignes par fichier au maximum, et les doublons de référence sont écartés, l'écran le mentionnant. Un fichier dont aucune ligne n'est exploitable est refusé en bloc, plutôt qu'importé à moitié.

Rappelons enfin la règle essentielle : une fiche déjà présente n'est jamais mise à jour. Votre gabarit ne peut donc pas devenir la source du barème, et le prix reste décidé dans Dolibarr.