# Guide d'Intégration du Système d'Autorisation Personnalisé

Le code source actuel prend en charge deux fournisseurs d'autorisation : Licentivo et `custom_http`. Les commerçants peuvent en sélectionner et activer un dans **Paramètres du système → Système d'autorisation**, et tous les produits et environnements sandbox/en direct utiliseront le même fournisseur ; les connexions personnalisées et les mappages de plans sont toujours configurés séparément par produit et environnement. Cette fonctionnalité n'inclut pas de système d'autorisation auto-construit ; les commerçants continuent d'utiliser leurs propres services d'autorisation et SDK.

L'échange de compte Licentivo existant, le SDK en neuf langues et la gestion des appareils continuent d'utiliser le processus original. Les produits personnalisés synchronisent le statut des achats selon ce protocole, et les clients lisent la livraison de l'autorisation dans le centre de compte sans passer par le processus SDK Licentivo.

## Démarrage rapide

1. Implémentez les quatre interfaces HTTP suivantes dans votre propre service, en configurant des clés et identifiants HMAC indépendants pour chaque connexion.
2. Dans le 'Système d'Authorization', sélectionnez 'Service d'Authorization Personnalisé', ajoutez une nouvelle connexion, remplissez l'adresse, l'environnement, l'identifiant de connexion, l'ID de clé et la clé standard Base64, vérifiez et enregistrez, puis cliquez sur 'Activer le Service d'Authorization Personnalisé'.
3. Créez des brouillons de produits et de plans de vente. Le type d'achat et le prix sont gérés dans Launch ; les règles d'authorization sont gérées dans votre propre système.
4. Sélectionnez le produit et l'environnement, sélectionnez la connexion, remplissez les références du produit et du plan externe, et enregistrez. Le fournisseur est déterminé par la sélection globale du site et ne peut pas activer Licentivo en même temps pour d'autres produits.
5. Publie une version test sur la page du plan de vente, complète les tests d’acceptation pour le paiement, la livraison, le renouvellement, la mise à niveau, le remboursement et la récupération des erreurs, puis configure l’environnement de production séparément.

Une fois que l’adresse, l’environnement, l’identifiant de connexion et la politique d’accès au réseau sont créés, ils ne peuvent plus être modifiés ; crée une nouvelle connexion si nécessaire. Les clés de signature peuvent être renouvelées ; laisse vide pour conserver la clé originale. Lorsqu’il y a des commandes sous un autre système d’autorisation, le fournisseur du site entier ne peut pas être changé directement ; la migration doit être complétée d’abord. Après le changement, le plan original devient uniquement en aperçu et doit être republier ; les identifiants et mappages de connexion enregistrés sont conservés. Les produits avec des commandes existantes ne peuvent pas changer de connexion directement, et les mappages dans les ventes ne peuvent pas être changés directement ; les nouveaux prix ou mappages utilisent une nouvelle version du plan.

Lors de la configuration via l'API, appelez d'abord `PUT /api/v1/admin/licensing/provider`, avec le corps de la requête étant `{"provider":"custom_http"}` ou `{"provider":"licentivo"}`, puis enregistrez la correspondance des produits. Les fournisseurs non activés ne peuvent pas enregistrer les liaisons d'autorisation de produit ni les correspondances de stratégie de plan Licentivo.

Les vérifications de connexion confirment les protocoles, l'authentification, l'environnement et les types d'achat pris en charge, et ne créeront ni ne confirmeront des packages externes au nom des commerçants. L'acceptation de l'intégration en sandbox doit être effectuée avec de vrais packages externes avant la collecte officielle des paiements.

## 3. Processus d'intégration du commerçant

1. Implémentez les quatre interfaces de ce document avant un système d'autorisation existant ; la logique de licence existante dans le système d'autorisation reste intacte.
2. Créez une connexion « Service d'autorisation personnalisé » dans Launch, en configurant l'adresse HTTPS, l'identifiant de connexion, l'environnement et la clé de signature.
3. Launch appelle l'interface de vérification des capacités pour confirmer la version du protocole, la prise en charge des achats permanents/temporaire et la méthode de livraison.
4. Sélectionnez cette connexion pour le produit ; mappez la version du package de vente sur `product_ref` et `plan_ref` externes. Les codes externes peuvent utiliser les propres chaînes du commerçant et n'ont pas besoin d'utiliser l'UUID Licentivo.
5. Complétez le paiement, le renouvellement, la mise à niveau, le remboursement et la récupération des pannes dans le bac à sable, puis publiez la configuration en direct séparément.
6. Après vérification du paiement, Launch enregistre la commande, la couverture d'achat et les tâches en attente dans la même transaction de base de données.
7. Le backend envoie un instantané complet de l'achat au Service d'Autorisation Personnalisé ; le service conserve l'enregistrement reçu avant de confirmer la réception.
8. Le service d'autorisation traite le statut cible. Launch interroge le reçu de l'événement original, et seul `applied` confirme que cette synchronisation est terminée.
9. Launch récupère les informations de livraison pour les clients connectés qui possèdent l'achat et affiche le code d'autorisation, l'entrée d'activation ou les instructions en texte clair dans le centre client. L'application se connecte au propre système d'autorisation du marchand.
10. Le renouvellement, la mise à niveau, le remboursement, le retrait, la suspension et l'expiration génèrent tous une nouvelle version de l'instantané complet. Le service d'autorisation traite selon le dernier statut cible.

Les connexions sandbox/live du même produit sont liées séparément. Après un achat existant, le changement de fournisseur nécessite un processus de migration indépendant ; les anciennes commandes et les anciennes tâches ne peuvent pas suivre la sélection du backend et être envoyées silencieusement au nouveau service.

## 4. APIs requises par le service d'autorisation personnalisé

Les interfaces suivantes sont **reçues par le service d'autorisation du commerçant et appelées par le serveur Launch**. `base_url` peut inclure un préfixe de chemin fixe ; les chemins dans le tableau sont ajoutés après ce préfixe.

| Méthodes et chemins | Usage |
| --- | --- |
| `GET /launch/v1/capabilities` | Vérifications de connexion, protocoles et déclarations de capacités |
| `POST /launch/v1/events` | Recevoir le statut complet de l'achat cible ; réessayer en utilisant l'événement original |
| `GET /launch/v1/events/{event_id}` | Vérifier si l'événement est réellement appliqué et restaurer les réponses perdues |
| `GET /launch/v1/entitlements/{entitlement_ref}/delivery` | Lire les informations de livraison déjà autorisées sans créer une deuxième autorisation |

La première édition utilise des reçus de requête actifs et n'ajoute pas de callbacks externes pour modifier les commandes. Les services personnalisés n'ont pas le pouvoir de confirmer le paiement, de changer les prix ou de réécrire les périodes d'achat.

### 4.1 Conventions générales et authentification

- JSON utilise UTF-8 ; le temps utilise UTC RFC 3339 ; `version` utilise des entiers positifs.
- Une réponse réussie est `{"data": {...}, "request_id": "..."}` ; une réponse d'erreur est `{"error": {"code": "...", "message": "..."}, "request_id": "..."}`.
- Chaque environnement et connexion utilise une clé séparée. La clé doit contenir au moins 32 octets aléatoires, référencés dans Launch depuis le stockage de clés chiffrées existant, et ne doit pas être placée dans la configuration front-end, les exemples ou les packages d'application.
- Chaque requête transporte `X-Launch-Key-Id`, `X-Launch-Timestamp`, `X-Launch-Nonce`, `X-Launch-Signature`.
- L'horodatage est en secondes Unix décimales. Le nonce est un encodage base64 d'au moins 128 bits de valeur aléatoire sans remplissage, et une nouvelle valeur est générée à chaque tentative réseau.

L'entrée de la signature se compose des sept éléments suivants, encodés en octets UTF-8 et reliés par un seul saut de ligne, sans saut de ligne final :

```text
v1
<key_id>
<timestamp>
<nonce>
<UPPERCASE HTTP method>
<encoded request path and raw query, including the base_url path prefix>
<lowercase hexadecimal SHA-256 digest of the exact request body>
```

`X-Launch-Signature` se compose du préfixe `v1=` suivi du résultat de `HMAC-SHA256` encodé en base64url sans remplissage. Le corps des requêtes GET est vide. Le destinataire vérifie la signature et la fenêtre temporelle avant la désérialisation, utilise une comparaison en temps constant, accepte par défaut un décalage de ±300 secondes et conserve les nonces pendant au moins 600 secondes pour empêcher le rejeu. Ne réutilisez pas les nonces entre les tentatives ; l’ID d’événement et le corps métier de la requête restent inchangés.

Les signatures ne remplacent pas HTTPS. Les requêtes ne contiennent pas de mots de passe clients, de clés de paiement ou de clés de gestion Licentivo ; par défaut, seules des références de compte anonymes sont envoyées, pas les adresses e-mail. Les clients HTTP du serveur ont besoin de délais d’attente, de limites de taille pour les réponses et du désactivation du transfert des identifiants lors des redirections. Les adresses personnalisées doivent être validées ; l’accès à l’intranet doit être configuré explicitement par les administrateurs.

### Vérification des capacités 4.2

`GET /launch/v1/capabilities` renvoie HTTP 200, par exemple :

```json
{
  "data": {
    "protocol": "launch-external-licensing-v1",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "purchase_types": ["perpetual", "fixed_term"],
    "delivery_types": ["license_key", "activation_url", "instructions"],
    "devices": false,
    "identity_exchange": false
  },
  "request_id": "check-001"
}
```

`protocol`, `connection_ref`, `environment`, `purchase_types`, `delivery_types` sont requis. Les deux derniers doivent être des tableaux non vides, et les types d’achat non pris en charge ne peuvent pas être listés à la vente. Les premières versions `devices` et `identity_exchange` doivent être `false` ; le support ne peut être déclaré qu’après la finalisation du contrat étendu et de la mise en œuvre.

### Instantané d’achat 4.3

Exemple de demande pour `POST /launch/v1/events` :

```json
{
  "schema": "launch-external-licensing-v1",
  "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
  "connection_ref": "merchant-01-sandbox",
  "environment": "sandbox",
  "type": "entitlement.snapshot",
  "change_reason": "purchase",
  "account_ref": "customer_opaque_001",
  "entitlement_ref": "purchase_opaque_001",
  "launch_product_id": "c6dd463e-50f7-4b76-8239-0a8ec6fc304b",
  "launch_plan_version_id": "5459a073-9362-4b46-949d-9b0c5d84421a",
  "product_ref": "desktop-editor",
  "plan_ref": "professional-yearly-v1",
  "version": 1,
  "desired_state": "active",
  "valid_from": "2026-10-25T00:00:00Z",
  "valid_until": "2027-10-25T00:00:00Z"
}
```

Tous les champs de l'exemple ci-dessus sont obligatoires. `valid_until` en tant que `null` signifie achat permanent ; les autres valeurs doivent être postérieures à `valid_from`.

| Champ | Contraintes et signification |
| --- | --- |
| `event_id` | UUID généré par Launch ; reste le même si le même événement est réessayé |
| `connection_ref` / `environment` | Doit correspondre à la connexion et à l'environnement de la clé de signature |
| `account_ref` | Référence de compte anonyme stable ; le destinataire identifie le client en utilisant la connexion, l'environnement et cette référence ensemble |
| `entitlement_ref` | Référence stable pour une relation d'achat ; si entièrement révoquée, une nouvelle référence est utilisée pour un nouvel achat |
| `launch_product_id` / `launch_plan_version_id` | Produit de Launch et UUID de version de package immuable |
| `product_ref` / `plan_ref` | Référence externe configurée par l'intégrateur ; caractères ASCII 1–128, lettres, chiffres, `_`, `-`, `.` autorisés ; les packages externes ne sont pas créés automatiquement |
| `version` | Version cible qui augmente de manière monotone sous la même connexion, environnement et référence d'achat |
| `desired_state` | `active`、`suspended`、`expired`、`revoked` |
| `change_reason` | `purchase`、`renewal`、`upgrade`、`refund`、`reversal`、`suspension`、`resumption`、`expiry`、`reconciliation` |

`account_ref`, `entitlement_ref` utilisent les lettres, chiffres, underscores ou tirets de 1–128. `schema`, `type` sont fixés aux valeurs d'exemple. Le récepteur applique les résultats selon `desired_state` et le terme absolu complet, sans ajouter de jours ni révoquer lui-même l'autorisation entière.

Surtout pour les remboursements : après avoir retourné une commande de renouvellement ou de mise à niveau, l'achat de base valide peut encore rester ; dans ce cas, la capture instantanée peut toujours être `active`, mais la durée est raccourcie ou le forfait change. Ce n'est qu'après que le grand livre est recalculé et qu'aucun achat valide ne le couvre que le statut de résiliation correspondant est envoyé.

### Réception, Accusé de réception et Récupération de 4.4

Le récepteur doit retourner HTTP 202 uniquement après avoir conservé l'ID de l'événement, le résumé du corps de la requête initiale et la tâche à exécuter ; les requêtes répétées qui ont déjà été synchronisées peuvent retourner HTTP 200. Les deux retourneront la structure d'accusé de réception suivante.

```json
{
  "data": {
    "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "payload_sha256": "<64-character lowercase hexadecimal SHA-256 digest of the exact event body>",
    "status": "pending",
    "error_code": null
  },
  "request_id": "receipt-001"
}
```

Remplacez le marqueur du condensat par le condensat réel. Tous les champs du reçu ci-dessus sont obligatoires ; `error_code` vaut `null` en l’absence d’erreur. `GET /launch/v1/events/{event_id}` renvoie la même structure avec HTTP 200. L’état peut être :

| `status` | Signification | Traitement de Launch |
| --- | --- | --- |
| `pending` | Persisté mais le changement d'autorisation réel n'est pas encore terminé | Requête retardée, impossible de marquer comme synchronisé |
| `applied` | Le service d'autorisation confirme que l'état cible a été effectivement appliqué | Synchronisation confirmée après vérification de tous les champs liés et du digest |
| `rejected` | Clairement non applicable, par ex. le paquet externe n'existe pas | Arrêter les tentatives automatiques, afficher une erreur claire |
| `manual_review` | Résultat incertain ou conflit d'état, nécessite une vérification manuelle | La tâche et l'événement d'origine sont conservés, en attente de traitement auditable |

Le statut de livraison est indépendant du reçu de synchronisation ; `applied` ne peut pas servir directement de permission opérationnelle pour l'application client.

Exigences de fiabilité :

- Livraison au moins une fois ; le récepteur doit garantir l'idempotence métier ; il ne peut pas promettre « appel unique » au niveau réseau.
- Retourner les résultats existants pour le même ID d'événement et le même corps de requête original ; retourner HTTP 409 `IDEMPOTENCY_CONFLICT` pour le même ID mais un corps de requête différent.
- Les anciennes versions ne doivent pas écraser les nouvelles versions. Différents événements ou cibles de la même version d'achat ne doivent pas être acceptés silencieusement et doivent renvoyer HTTP 409 `STATE_VERSION_CONFLICT` pour vérification.
- Si une réponse POST est perdue ou si le processus redémarre, Launch effectue d'abord un GET sur l'événement original ; ce n'est qu'après avoir explicitement renvoyé `EVENT_NOT_FOUND` que l'ID original et le corps de requête doivent être renvoyés.
- Si le résultat réel de la livraison à distance est incertain, l'intégrateur doit également utiliser sa propre référence idempotente persistée pour vérification et ne peut pas générer une autre autorisation en renvoyant simplement une fois.
- HTTP 429 suit `Retry-After` ; les erreurs de délai d'attente et 5xx se récupèrent avec une reprise progressive ; dépasser la limite nécessite une vérification manuelle. L'authentification, la configuration et les rejets commerciaux ne doivent pas être considérés comme une synchronisation réussie.

Codes d'erreur suggérés : `SIGNATURE_INVALID` (401), `SIGNATURE_EXPIRED` (401), `NONCE_REPLAYED` (409), `SCOPE_MISMATCH` (403), `EVENT_NOT_FOUND` (404), `IDEMPOTENCY_CONFLICT` (409), `STATE_VERSION_CONFLICT` (409), `PLAN_UNKNOWN` (422), `PURCHASE_TYPE_UNSUPPORTED` (422), `RATE_LIMITED` (429), `SERVICE_UNAVAILABLE` (503). Les messages d'erreur ne doivent pas inclure d’identifiants.

### 4.5 Livraison Autorisée

`GET /launch/v1/entitlements/{entitlement_ref}/delivery` peut uniquement lire les résultats de livraison existants dans cette connexion et cet environnement et ne doit pas considérer les requêtes comme de nouvelles commandes d'émission. Exemple HTTP 200 :

```json
{
  "data": {
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "status": "ready",
    "delivery": {
      "type": "license_key",
      "value": "EXAMPLE-ONLY-NOT-A-REAL-LICENSE",
      "expires_at": null
    }
  },
  "request_id": "delivery-001"
}
```

`status` est `pending`, `ready` ou `unavailable` ; lorsqu’il n’est pas prêt, `delivery` est `null`. Lorsqu’il est prêt, `type` doit être un `license_key`, `activation_url` ou `instructions` déclaré, avec `value` jusqu’à 4096 UTF-8 octets, et `expires_at` en temps UTC ou `null`. Tous les champs liés dans la réponse doivent correspondre à la version actuelle achetée dans Launch ; les livraisons des anciennes versions ne peuvent pas être considérées comme des résultats actuels.

Les codes d'autorisation sont des données sensibles ; les réponses de livraison utilisent `Cache-Control: no-store`, ne loguez pas le corps et elles doivent être stockées chiffrées si une persistance est nécessaire. Les URL d'activation acceptent uniquement les sources HTTPS autorisées par la configuration de la connexion ; les instructions sont affichées en texte brut, sans exécuter de HTML ni de scripts. Les comptes qui sont révoqués, entièrement remboursés ou sans achat valide ne peuvent pas continuer à obtenir des infos de livraison via Launch ; l'expiration réelle des identifiants livrés est gérée par des systèmes externes.

## Gestion Launch et API client

Ces interfaces sont sous l'origine Launch déployée par le marchand et n’utilisent pas l’authentification HMAC via un service externe. Les admins utilisent les sessions existantes du site web, et les requêtes d’écriture doivent inclure `X-CSRF-Token`. Les clients peuvent seulement lire leurs propres achats.

| Méthodes et chemins | Usage |
| --- | --- |
| `GET /api/v1/admin/licensing/connections` | Retourne `connections`, `bindings`, `mappings` et `default_provider` ; La liste de connexions contient uniquement des connexions personnalisées, Licentivo l'interface de gestion d'origine est conservée |
| `POST /api/v1/admin/licensing/connections` | Vérifier et ajouter des connexions personnalisées |
| `PUT /api/v1/admin/licensing/connections/{id}` | Modifier le nom, les sources d'activation autorisées et la configuration de la signature ; L'adresse de réception historique reste inchangée |
| `POST /api/v1/admin/licensing/connections/{id}/check` | Reverifier ; `last_check_error` en HTTP 200 qui n'est pas null signifie que la vérification a échoué |
| `PUT /api/v1/admin/products/{id}/licensing` | Fournit le fournisseur de la configuration de l'environnement pour le produit, mappant les connexions aux packages externes |
| `GET /api/v1/me/products/{id}/delivery?environment=sandbox` | Le client actuel lit la livraison d'autorisation d'achat personnalisée actuellement valide ; Authentification par cookie ou par porteur de bureau |
| `GET /api/v1/licensing/documentation` | Ce document ne nécessite pas de connexion |
| `GET /api/v1/licensing/protocol` | Le OpenAPI requis pour les services personnalisés ne nécessite pas de connexion |

Ajouter un corps de requête de connexion personnalisée :

```json
{
  "provider": "custom_http",
  "name": "My licensing service",
  "base_url": "https://licenses.example.com",
  "environment": "sandbox",
  "connection_ref": "merchant-01-sandbox",
  "key_id": "key-1",
  "signing_key": "<standard Base64 encoding of 32 to 128 random bytes>",
  "allow_private_network": false,
  "allow_local_http": false,
  "activation_origins": ["https://activate.example.com"]
}
```

La réponse `data` est l'objet de connexion désensibilisé et ne renvoie ni la clé de signature ni l'identifiant secret. allow_local_http ne permet que l'adresse localhost/loopback du sandbox ; HTTPS privé nécessite une autorisation explicite de l'administrateur. Laisser la clé vide n'est autorisé que pour modifier des connexions existantes. Licentivo est encore configuré via `POST /api/v1/admin/connections/licentivo`.

Configurez le corps de la demande du produit :

```json
{
  "provider": "custom_http",
  "environment": "sandbox",
  "connection_id": "<id returned when creating the connection>",
  "mappings": [
    {
      "version_id": "<Launch plan version UUID>",
      "product_ref": "desktop-editor",
      "plan_ref": "professional-yearly-v1"
    }
  ]
}
```

Lorsque Licentivo est sélectionné, `{"provider":"licentivo","environment":"sandbox","mappings":[]}` est envoyé, et son mappage produit/politique utilise toujours l'interface originale. Le premier interrupteur transforme une version existante en aperçu, ce qui nécessite une nouvelle vérification et publication par le nouveau fournisseur. Les mappages ne peuvent inclure que la version à ajouter ou à conserver ; les omettre ne supprimera pas les anciens mappages.

La livraison client est l'enveloppe de données standard pour HTTP 200 ; pas de retours d'achat 404, Licentivo, retour de marchandises 409 `LICENSING_DELIVERY_UNSUPPORTED`, non synchronisé, suspendu, expiré ou retours avec remboursement complet 409 `ENTITLEMENT_NOT_READY`, externe indisponible ou retours avec réception non correspondante 503. La fonction « Voir la livraison autorisée » du Centre Client revalide la propriété et le statut, sans écrire les identifiants dans les URL ou les journaux.

L'identité, l'appareil et les interfaces de vérification d'intégration du produit personnalisé Licentivo renvoient 409 `CUSTOM_LICENSING_SDK_REQUIRED`. L'ancien `/credential` continue de renvoyer 410. Les applications doivent utiliser le SDK du commerçant lui-même, plutôt que de considérer le statut Launch `ready` comme une autorisation à l'exécution.

## Exemples et Acceptation

[Python exemple d'adaptateur léger ](/fr/launch-documents/custom-licensing-example/) fournit un cadre de réception avec signature, nonce, déduplication d'événements et persistance des reçus ; la gestion réelle de l'autorisation nécessite une intégration avec le système du commerçant.

Le protocole personnalisé [OpenAPI](/fr/assets/launch/external-licensing-openapi.json) et Launch possède [OpenAPI](/fr/assets/launch/openapi.json) décrivent les deux directions séparément. Le récepteur n'a pas besoin de mettre en œuvre le catalogue de produits, le catalogue de politiques ou les interfaces de confiance d'identité de Licentivo.

La vérification locale utilise le MySQL isolé, un vrai adaptateur HTTP, et des services d'autorisation/paiement simulés, couvrant la perte de réponse, le redémarrage, le renouvellement, la mise à jour, le remboursement, la désactivation de compte, la non-correspondance des reçus et l’isolation de la livraison. Pour l’acceptation en production, le service d’autorisation et l’application du marchand sont également requis. Les appareils non enregistrés, la connexion avec un compte partagé et les fonctionnalités hors ligne ne sont pas couverts par Launch.
