> ## Documentation Index
> Fetch the complete documentation index at: https://chariow.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Intégration Licence SaaS

> Guide complet pour implémenter un paywall basé sur les licences dans votre application SaaS avec l'API Chariow et les outils d'IA

Ce guide vous montre comment utiliser l'API Licence de Chariow pour implémenter un paywall dans votre application SaaS. Que vous construisiez avec Lovable, Cursor, Bolt ou tout autre assistant de codage IA, ce guide fournit tout ce dont vous avez besoin, y compris des prompts IA prêts à l'emploi.

<Note>
  Ce guide suppose que vous avez une boutique Chariow avec un produit de type **Licence** créé. Si vous n'en avez pas encore, [créez votre produit licence](https://app.chariow.com) d'abord.
</Note>

## Qu'est-ce qu'un Paywall par Licence ?

Un paywall par licence restreint l'accès à votre application SaaS (ou à des fonctionnalités spécifiques) jusqu'à ce que l'utilisateur fournisse une clé de licence valide. C'est idéal pour :

* **Applications de bureau** : Applications Electron, logiciels natifs, outils CLI
* **Applications web** : Plateformes SaaS, tableaux de bord admin, outils premium
* **Applications mobiles** : Applications iOS/Android avec fonctionnalités premium
* **Accès API** : Contrôle d'accès API basé sur la validité de la licence

### Comment ça fonctionne

<Steps>
  <Step title="Le client achète la licence">
    Le client achète votre produit licence sur Chariow. Une clé de licence unique est automatiquement générée (ex: `ABC-123-XYZ-789`).
  </Step>

  <Step title="Le client entre sa clé de licence">
    Dans votre application, le client saisit sa clé de licence dans un écran de paramètres ou d'activation.
  </Step>

  <Step title="Votre application valide la clé">
    Votre application appelle l'API Chariow pour valider la clé de licence et vérifier son statut.
  </Step>

  <Step title="Accès accordé ou refusé">
    Selon la réponse de l'API (`is_active`, `is_expired`), votre application accorde ou refuse l'accès.
  </Step>

  <Step title="Optionnel : Activation sur l'appareil">
    Pour les licences limitées par appareil, activez la licence pour suivre et limiter l'utilisation.
  </Step>
</Steps>

## Points d'accès API nécessaires

| Point d'accès                 | Méthode | Objectif                                         |
| ----------------------------- | ------- | ------------------------------------------------ |
| `/v1/licenses/{key}`          | GET     | Valider une clé de licence et vérifier le statut |
| `/v1/licenses/{key}/activate` | POST    | Activer la licence sur un appareil               |

<Info>
  Votre clé API (`sk_live_...`) doit être conservée **côté serveur uniquement**. Ne l'exposez jamais dans le code côté client.
</Info>

## Modèles d'architecture

### Modèle 1 : Validation côté serveur (Recommandé)

Votre backend valide les licences et contrôle l'accès. Idéal pour les applications web.

<Frame>
  <img src="https://mintcdn.com/chariow/H6ouyZI6trlStJ7k/images/license-validation-flow.svg?fit=max&auto=format&n=H6ouyZI6trlStJ7k&q=85&s=9c0bba4afbf73ddbb3137308c2f510bb" alt="Flux de validation de licence côté serveur" width="800" height="380" data-path="images/license-validation-flow.svg" />
</Frame>

### Modèle 2 : Validation Serverless/Edge

Validez les licences à la périphérie avec des fonctions serverless. Bon pour les sites statiques.

<Frame>
  <img src="https://mintcdn.com/chariow/H6ouyZI6trlStJ7k/images/static-site-flow.svg?fit=max&auto=format&n=H6ouyZI6trlStJ7k&q=85&s=1a4bf98c1412aeb5743121c9367868fb" alt="Flux de validation de licence serverless" width="700" height="160" data-path="images/static-site-flow.svg" />
</Frame>

### Modèle 3 : Application de bureau avec validation périodique

Les applications de bureau valident au démarrage et périodiquement. Inclut une période de grâce hors ligne.

<Frame>
  <img src="https://mintcdn.com/chariow/H6ouyZI6trlStJ7k/images/desktop-app-flow.svg?fit=max&auto=format&n=H6ouyZI6trlStJ7k&q=85&s=c5db70877e81f619cd881fa9bf45bb44" alt="Flux de validation de licence application bureau avec cache local" width="500" height="280" data-path="images/desktop-app-flow.svg" />
</Frame>

## Guide d'implémentation

### Étape 1 : Créer une route API pour la validation de licence

Votre backend doit exposer un point d'accès que votre frontend appelle :

```javascript theme={null}
// /api/validate-license.js (route API Next.js)
export default async function handler(req, res) {
  const { licenseKey } = req.body;

  if (!licenseKey) {
    return res.status(400).json({ valid: false, error: 'Clé de licence requise' });
  }

  try {
    const response = await fetch(
      `https://api.chariow.com/v1/licenses/${encodeURIComponent(licenseKey)}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.CHARIOW_API_KEY}`
        }
      }
    );

    if (!response.ok) {
      return res.status(200).json({ valid: false, error: 'Clé de licence invalide' });
    }

    const { data } = await response.json();

    // Vérifier la validité de la licence
    if (!data.is_active) {
      return res.status(200).json({
        valid: false,
        error: 'La licence n\'est pas active'
      });
    }

    if (data.is_expired) {
      return res.status(200).json({
        valid: false,
        error: 'La licence a expiré'
      });
    }

    // La licence est valide
    return res.status(200).json({
      valid: true,
      license: {
        status: data.status,
        expiresAt: data.expires_at,
        activationsRemaining: data.activations?.remaining
      }
    });

  } catch (error) {
    console.error('Erreur de validation de licence:', error);
    return res.status(500).json({ valid: false, error: 'Échec de la validation' });
  }
}
```

### Étape 2 : Créer le composant de saisie de licence

```jsx theme={null}
// components/LicenseGate.jsx
import { useState, useEffect } from 'react';

export function LicenseGate({ children }) {
  const [isValidated, setIsValidated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [licenseKey, setLicenseKey] = useState('');
  const [error, setError] = useState('');

  // Vérifier la licence stockée au montage
  useEffect(() => {
    const storedKey = localStorage.getItem('license_key');
    if (storedKey) {
      validateLicense(storedKey);
    } else {
      setIsLoading(false);
    }
  }, []);

  const validateLicense = async (key) => {
    setIsLoading(true);
    setError('');

    try {
      const response = await fetch('/api/validate-license', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ licenseKey: key })
      });

      const data = await response.json();

      if (data.valid) {
        localStorage.setItem('license_key', key);
        setIsValidated(true);
      } else {
        setError(data.error || 'Licence invalide');
        localStorage.removeItem('license_key');
      }
    } catch (err) {
      setError('Échec de la validation de licence');
    } finally {
      setIsLoading(false);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    validateLicense(licenseKey);
  };

  if (isLoading) {
    return <div className="license-loading">Validation de la licence...</div>;
  }

  if (isValidated) {
    return children;
  }

  return (
    <div className="license-gate">
      <h2>Entrez votre clé de licence</h2>
      <p>Veuillez saisir votre clé de licence pour accéder à l'application.</p>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={licenseKey}
          onChange={(e) => setLicenseKey(e.target.value)}
          placeholder="ABC-123-XYZ-789"
        />
        <button type="submit">Activer</button>
      </form>

      {error && <p className="error">{error}</p>}

      <p className="help-text">
        Vous n'avez pas de licence ? <a href="https://votreboutique.chariow.com">Achetez-en une ici</a>
      </p>
    </div>
  );
}
```

### Étape 3 : Envelopper votre application avec le contrôle de licence

```jsx theme={null}
// App.jsx
import { LicenseGate } from './components/LicenseGate';
import { Dashboard } from './components/Dashboard';

function App() {
  return (
    <LicenseGate>
      <Dashboard />
    </LicenseGate>
  );
}
```

## Activation sur appareil (Optionnel)

Si votre licence a des activations limitées, activez sur l'appareil :

```javascript theme={null}
// /api/activate-license.js
export default async function handler(req, res) {
  const { licenseKey, deviceId } = req.body;

  const response = await fetch(
    `https://api.chariow.com/v1/licenses/${encodeURIComponent(licenseKey)}/activate`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.CHARIOW_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        device_identifier: deviceId
      })
    }
  );

  const data = await response.json();

  if (!response.ok) {
    return res.status(400).json({
      success: false,
      error: data.message
    });
  }

  return res.status(200).json({
    success: true,
    activationsRemaining: data.data.activations?.remaining
  });
}
```

## Prompts IA pour l'implémentation

Utilisez ces prompts avec Lovable, Cursor, Bolt ou tout assistant de codage IA pour implémenter rapidement le paywall par licence.

### Prompt 1 : Paywall de licence basique (React + Next.js)

```text theme={null}
Créer un système de paywall par licence pour mon application Next.js avec ces exigences :

1. Créer une route API à /api/validate-license qui :
   - Accepte les requêtes POST avec { licenseKey: string }
   - Appelle l'API Chariow : GET https://api.chariow.com/v1/licenses/{licenseKey}
   - Utilise l'authentification Bearer token avec la variable d'environnement CHARIOW_API_KEY
   - Retourne { valid: true/false, error?: string, license?: object }
   - Vérifie les champs is_active et is_expired de la réponse

2. Créer un composant LicenseGate qui :
   - Affiche un formulaire de saisie de clé de licence quand non validé
   - Stocke la clé de licence valide dans localStorage
   - Vérifie localStorage au montage pour auto-valider les utilisateurs récurrents
   - Affiche les enfants uniquement quand la licence est valide
   - Affiche un état de chargement pendant la validation
   - Affiche les messages d'erreur pour les licences invalides

3. Styliser le formulaire de licence avec Tailwind CSS :
   - Centrer le formulaire sur la page
   - Utiliser un style propre et professionnel
   - Inclure un lien pour acheter une licence

Variable d'environnement nécessaire : CHARIOW_API_KEY (clé API boutique)
```

### Prompt 2 : Paywall de licence avec activation d'appareil

```text theme={null}
Étendre le système de paywall par licence avec l'activation d'appareil :

1. Générer un identifiant d'appareil unique :
   - Pour les apps web : combiner user agent + résolution d'écran + fuseau horaire
   - Hasher le résultat pour créer un ID d'appareil cohérent
   - Stocker l'ID d'appareil dans localStorage

2. Créer le point d'accès /api/activate-license :
   - Accepte POST avec { licenseKey, deviceId }
   - Appelle l'API Chariow : POST https://api.chariow.com/v1/licenses/{key}/activate
   - Corps : { device_identifier: deviceId }
   - Gérer l'erreur "Limite d'activation atteinte" gracieusement

3. Mettre à jour le composant LicenseGate :
   - Après validation de la licence, appeler le point d'activation
   - Afficher le nombre d'activations restantes à l'utilisateur
   - Gérer les erreurs d'activation avec des messages conviviaux

4. Ajouter une section "Gérer les appareils" affichant :
   - Nombre d'activations actuel
   - Nombre maximum d'activations autorisées
   - Option pour désactiver l'appareil actuel
```

### Prompt 3 : Licence basée sur les fonctionnalités

```text theme={null}
Implémenter une licence à fonctionnalités contrôlées où différents niveaux de licence débloquent différentes fonctionnalités :

1. Créer un hook useLicense qui :
   - Valide la licence au chargement de l'app
   - Retourne { isValid, tier, features, expiresAt }
   - Le niveau est déterminé par le nom du produit ou les métadonnées de Chariow

2. Créer un composant FeatureGate :
   - Accepte la prop requiredTier (ex : "pro", "enterprise")
   - Affiche une invite de mise à niveau si le niveau de l'utilisateur est insuffisant
   - Affiche les enfants si l'exigence de niveau est satisfaite

3. Exemple d'utilisation :
   <FeatureGate requiredTier="pro">
     <AdvancedAnalytics />
   </FeatureGate>

4. Créer une modale de mise à niveau qui :
   - Affiche le niveau actuel vs requis
   - Liens vers le checkout Chariow pour le produit de mise à niveau
   - Inclut une comparaison des fonctionnalités
```

### Prompt 4 : Validation de licence compatible hors ligne

```text theme={null}
Ajouter le support hors ligne au système de licence :

1. Mettre en cache le résultat de validation de licence localement :
   - Stocker l'horodatage de validation
   - Stocker la date d'expiration de la licence
   - Stocker l'objet licence complet

2. Implémenter la logique de validation :
   - Si en ligne : valider avec l'API Chariow
   - Si hors ligne : vérifier la validation en cache (valide pendant 7 jours)
   - Si le cache a expiré : afficher un avertissement hors ligne mais permettre un accès limité

3. Ajouter une re-validation périodique :
   - Re-valider toutes les 24 heures quand en ligne
   - Mettre à jour le cache lors d'une validation réussie
   - Gérer les erreurs réseau gracieusement

4. Afficher un indicateur de statut de licence :
   - Vert : validé en ligne aujourd'hui
   - Jaune : utilisation de la validation en cache
   - Rouge : cache expiré, connexion nécessaire
```

### Prompt 5 : Implémentation complète pour application Electron

```text theme={null}
Créer un système de licence pour une application de bureau Electron :

1. Gestionnaire de licence dans le processus principal :
   - Stocker la licence dans electron-store (chiffré)
   - Valider au lancement de l'app
   - Re-valider toutes les 24 heures
   - Envoyer le statut de validation au renderer via IPC

2. Script preload :
   - Exposer des APIs de licence sécurisées au renderer
   - licenseAPI.validate(key)
   - licenseAPI.getStatus()
   - licenseAPI.activate()

3. UI de licence dans le renderer :
   - Écran de saisie de licence au premier lancement
   - Statut de licence dans les paramètres
   - Option "Gérer les activations"

4. Identifiant d'appareil :
   - Utiliser le package machine-id pour un ID d'appareil cohérent
   - Passer au point d'activation Chariow

5. Gestion hors ligne :
   - Mettre en cache la validation pendant 7 jours
   - Afficher l'indicateur "Mode hors ligne"
   - Bloquer l'app après expiration du cache
```

### Prompt 6 : Intégration Supabase

```text theme={null}
Intégrer la validation de licence avec l'authentification Supabase :

1. Créer une fonction edge Supabase à /validate-license :
   - Vérifier que l'utilisateur est authentifié
   - Stocker les licences validées dans une table 'user_licenses'
   - Lier la licence à l'ID utilisateur

2. Schéma de base de données :
   CREATE TABLE user_licenses (
     id UUID PRIMARY KEY,
     user_id UUID REFERENCES auth.users,
     license_key TEXT NOT NULL,
     validated_at TIMESTAMP,
     expires_at TIMESTAMP,
     status TEXT
   );

3. Hook React useUserLicense :
   - Vérifier si l'utilisateur actuel a une licence valide
   - Synchroniser le statut de licence à la connexion
   - Gérer les scénarios multi-appareils

4. Row Level Security :
   - Les utilisateurs ne peuvent voir que leurs propres licences
   - Activer les abonnements realtime pour les mises à jour de statut
```

## Bonnes pratiques de sécurité

<Warning>
  N'exposez jamais votre clé API Chariow dans le code côté client. Validez toujours les licences via votre backend.
</Warning>

### À faire

* **Stocker les clés API côté serveur** dans les variables d'environnement
* **Valider côté backend** avant d'accorder l'accès
* **Mettre en cache les résultats de validation** pour réduire les appels API
* **Implémenter une limitation de débit** sur votre point de validation
* **Utiliser HTTPS** pour toutes les communications API
* **Journaliser les tentatives de validation** pour l'audit de sécurité

### À ne pas faire

* **Ne pas faire confiance à la validation côté client seule** - elle peut être contournée
* **Ne pas stocker l'objet licence complet** dans un localStorage accessible
* **Ne pas ignorer la validation** pour les utilisateurs "de confiance"
* **Ne pas coder en dur les clés de licence** dans votre application
* **Ne pas exposer des messages d'erreur détaillés** qui pourraient aider les attaquants

## Tester votre intégration

### Scénarios de test

| Scénario                     | Comportement attendu                                           |
| ---------------------------- | -------------------------------------------------------------- |
| Licence valide et active     | Accès accordé                                                  |
| Clé de licence invalide      | Afficher erreur "Licence invalide"                             |
| Licence expirée              | Afficher message "Licence expirée" avec lien de renouvellement |
| Licence révoquée             | Afficher "Licence révoquée" - contacter le support             |
| Erreur réseau                | Afficher le résultat en cache ou avertissement hors ligne      |
| Limite d'activation atteinte | Afficher "Trop d'appareils" avec options de gestion            |

### Clés de licence de test

Pendant le développement, créez des produits de test dans votre boutique Chariow avec :

* Des licences de test gratuites pour le développement
* Des périodes d'expiration courtes pour tester la gestion d'expiration
* Des activations limitées pour tester les limites d'appareils

## Exemple complet : Next.js App Router

Voici une implémentation complète utilisant Next.js App Router :

<CodeGroup>
  ```typescript app/api/license/validate/route.ts theme={null}
  import { NextResponse } from 'next/server';

  export async function POST(request: Request) {
    const { licenseKey } = await request.json();

    if (!licenseKey) {
      return NextResponse.json(
        { valid: false, error: 'Clé de licence requise' },
        { status: 400 }
      );
    }

    try {
      const response = await fetch(
        `https://api.chariow.com/v1/licenses/${encodeURIComponent(licenseKey)}`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.CHARIOW_API_KEY}`
          }
        }
      );

      if (!response.ok) {
        return NextResponse.json({ valid: false, error: 'Clé de licence invalide' });
      }

      const { data } = await response.json();

      if (!data.is_active) {
        return NextResponse.json({ valid: false, error: 'La licence n\'est pas active' });
      }

      if (data.is_expired) {
        return NextResponse.json({ valid: false, error: 'La licence a expiré' });
      }

      return NextResponse.json({
        valid: true,
        license: {
          status: data.status,
          expiresAt: data.expires_at,
          activationsRemaining: data.activations?.remaining
        }
      });
    } catch (error) {
      return NextResponse.json(
        { valid: false, error: 'Échec de la validation' },
        { status: 500 }
      );
    }
  }
  ```

  ```typescript hooks/useLicense.ts theme={null}
  'use client';

  import { useState, useEffect, useCallback } from 'react';

  interface LicenseState {
    isLoading: boolean;
    isValid: boolean;
    error: string | null;
    expiresAt: string | null;
  }

  export function useLicense() {
    const [state, setState] = useState<LicenseState>({
      isLoading: true,
      isValid: false,
      error: null,
      expiresAt: null
    });

    const validate = useCallback(async (key: string): Promise<boolean> => {
      setState(s => ({ ...s, isLoading: true, error: null }));

      try {
        const res = await fetch('/api/license/validate', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ licenseKey: key })
        });

        const data = await res.json();

        if (data.valid) {
          localStorage.setItem('license_key', key);
          setState({
            isLoading: false,
            isValid: true,
            error: null,
            expiresAt: data.license?.expiresAt
          });
          return true;
        } else {
          localStorage.removeItem('license_key');
          setState({
            isLoading: false,
            isValid: false,
            error: data.error,
            expiresAt: null
          });
          return false;
        }
      } catch {
        setState(s => ({
          ...s,
          isLoading: false,
          error: 'Échec de la validation'
        }));
        return false;
      }
    }, []);

    const logout = useCallback(() => {
      localStorage.removeItem('license_key');
      setState({
        isLoading: false,
        isValid: false,
        error: null,
        expiresAt: null
      });
    }, []);

    useEffect(() => {
      const storedKey = localStorage.getItem('license_key');
      if (storedKey) {
        validate(storedKey);
      } else {
        setState(s => ({ ...s, isLoading: false }));
      }
    }, [validate]);

    return { ...state, validate, logout };
  }
  ```

  ```tsx components/LicenseGate.tsx theme={null}
  'use client';

  import { useState } from 'react';
  import { useLicense } from '@/hooks/useLicense';

  export function LicenseGate({ children }: { children: React.ReactNode }) {
    const { isLoading, isValid, error, validate } = useLicense();
    const [keyInput, setKeyInput] = useState('');

    if (isLoading) {
      return (
        <div className="flex items-center justify-center min-h-screen">
          <p>Validation de la licence...</p>
        </div>
      );
    }

    if (isValid) {
      return <>{children}</>;
    }

    return (
      <div className="flex flex-col items-center justify-center min-h-screen p-4">
        <div className="w-full max-w-md space-y-6">
          <div className="text-center">
            <h1 className="text-2xl font-bold">Entrez votre clé de licence</h1>
            <p className="text-gray-600 mt-2">
              Veuillez saisir votre clé de licence pour accéder à l'application.
            </p>
          </div>

          <form
            onSubmit={(e) => {
              e.preventDefault();
              validate(keyInput);
            }}
            className="space-y-4"
          >
            <input
              type="text"
              value={keyInput}
              onChange={(e) => setKeyInput(e.target.value)}
              placeholder="ABC-123-XYZ-789"
              className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
            />
            <button
              type="submit"
              className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
            >
              Activer la licence
            </button>
          </form>

          {error && (
            <p className="text-red-600 text-center">{error}</p>
          )}

          <p className="text-center text-gray-500 text-sm">
            Vous n'avez pas de licence ?{' '}
            <a href="https://votreboutique.chariow.com" className="text-blue-600 hover:underline">
              Achetez-en une ici
            </a>
          </p>
        </div>
      </div>
    );
  }
  ```
</CodeGroup>

## Prochaines étapes

<CardGroup cols={2}>
  <Card title="Guide des licences" icon="key" href="/fr/guides/licenses">
    En savoir plus sur la gestion des licences
  </Card>

  <Card title="API Licences" icon="code" href="/api-reference/licenses/list-licenses">
    Voir la référence complète de l'API Licences
  </Card>

  <Card title="Pulses (Webhooks)" icon="bolt" href="/fr/guides/pulses">
    Être notifié quand des licences sont achetées
  </Card>

  <Card title="Bonnes pratiques" icon="star" href="/fr/guides/best-practices">
    Bonnes pratiques de sécurité et d'intégration
  </Card>
</CardGroup>
