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

# Stripe Billing

> Activez les abonnements Stripe par utilisateur (Gratuit + Pro) avec des quotas de tokens et une gestion de forfait en libre-service.

FIM One propose un pipeline de facturation Stripe complet derrière un modèle d'accès par instance. Les déploiements privés le laissent sur **Pas d'abonnements** et ne voient jamais l'interface de paiement. Les opérateurs qui souhaitent un catalogue choisissent **Inclus + payant** ou **Payant uniquement** et obtiennent Checkout hébergé, Customer Portal, cycle de vie d'abonnement piloté par webhook et application des quotas.

<Note>
  La valeur par défaut du logiciel est **Pas d'abonnements** (`access_model = off`, `default_token_quota = 0` signifiant illimité). Les nouvelles installations et les auto-hébergements existants commencent là. Aucune interface de facturation n'apparaît jusqu'à ce qu'un administrateur choisisse une posture Stripe.
</Note>

## Ce que vous obtenez

* **Trois modèles d'accès** : sans abonnement, inclus + payant, ou payant uniquement
* **Stripe Checkout hébergé** — les utilisateurs passent à la version payante sans que vos données de carte ne transitent par votre code
* **Portail client** — les utilisateurs mettent à jour leurs méthodes de paiement, téléchargent des factures, annulent — tout sur l'interface de Stripe
* **Cycle de vie piloté par webhooks** — les abonnements se provisionnent et se renouvellent automatiquement ; les utilisateurs payants annulés reviennent au niveau inclus (ou deviennent non autorisés en mode payant uniquement) à la fin de la période
* **Application des quotas** — utilisation des tokens suivie par période ; interruption en cours de flux avec une invite de mise à niveau structurée
* **Pages d'administration** pour la gestion des plans et la surveillance des abonnements

## Prérequis

1. **Compte Stripe** avec le mode Live activé. Les entreprises constituées à Singapour doivent compléter la vérification KYC (UEN commercial, ID du directeur, compte bancaire). L'approbation prend généralement 1 à 3 jours.
2. **Clé API Stripe Live** de type Restricted (recommandée plutôt que Standard `sk_live_***` — plus facile à révoquer, permissions limitées).
3. **Point de terminaison webhook** accessible publiquement à `<your-domain>/api/webhooks/stripe`.
4. **Compte bancaire** pour les versements. Le règlement multi-devises (par ex. versement en USD vers un compte USD) est recommandé pour les comptes Stripe non-USD par défaut afin d'éviter une fuite de change de 1,5 à 2 % par transaction.

## Configuration

### 1. Tableau de bord Stripe

#### Créer le produit Pro

1. **Catalog → Products → + Add product**
2. Name: `Pro`, description: `5M tokens / month, priority support`
3. Pricing: Recurring, monthly, \$20.00 USD (adjust to your pricing strategy)
4. Save → copy the resulting `price_***` ID (you will UPDATE the local `billing_plans` table with this value after activation)

#### Créer une clé API restreinte

1. **Developers → API keys → + Create restricted key**
2. Name: `fim-one production`
3. Permissions (minimum):
   * **Customers**: Write
   * **Subscriptions**: Write
   * **Checkout Sessions**: Write
   * **Customer portal**: Write
   * **Prices**: Read
   * **Products**: Read
4. Save → copy `rk_live_***`

#### Enregistrer le point de terminaison du webhook

1. **Developers → Webhooks → + Add endpoint**
2. URL: `https://<your-domain>/api/webhooks/stripe`
3. Événements à recevoir:
   * `checkout.session.completed`
   * `customer.subscription.created`
   * `customer.subscription.updated`
   * `customer.subscription.deleted`
   * `invoice.payment_succeeded`
   * `invoice.payment_failed`
4. Après l'enregistrement, cliquez sur "Reveal signing secret" → copiez `whsec_***`

#### Configurer le règlement multi-devises (recommandé)

Si la devise par défaut de votre compte Stripe diffère de la devise de prix que vous facturez (cas courant : compte SGD facturant en USD) :

1. **Paramètres → Comptes bancaires et devises → Ajouter une devise de règlement**
2. Sélectionnez la devise de prix (par ex. USD)
3. Attachez le compte bancaire correspondant (par ex. un compte virtuel Aspire USD)
4. Enregistrez — Stripe achemine les frais USD directement vers les versements USD, sans conversion de change

### 2. Backend `.env`

Définissez ces trois clés dans votre `.env` de production :

```bash theme={null}
STRIPE_SECRET_KEY=rk_live_***
STRIPE_WEBHOOK_SECRET=whsec_***
STRIPE_BILLING_RETURN_URL=https://<your-domain>/settings?tab=billing
```

Consultez [Variables d'environnement](/configuration/environment-variables) pour la référence complète.

Redémarrez le backend après avoir modifié `.env` pour que les clés soient prises en compte :

```bash theme={null}
./deploy.sh   # or: docker compose restart fim-one
```

### 3. Activer dans Admin

1. Connectez-vous en tant qu'administrateur
2. **Admin → System Settings → Access and billing**
3. Choisissez une posture (l'instance est toujours dans exactement une) :
   * **No subscriptions** (logiciel par défaut) — Stripe inutilisé. L'accès par token est le quota mensuel par défaut (`0` = illimité).
   * **Included + paid** — Stripe requis. Les nouveaux utilisateurs arrivent sur le tier inclus ; ils peuvent passer à une version payante. Les utilisateurs payants annulés reviennent au tier inclus à la fin de la période.
   * **Paid only** — Stripe requis. Pas de tier inclus. Les utilisateurs doivent s'abonner avant de pouvoir appeler les modèles. Les utilisateurs du tier inclus existants conservent leur quota si vous passez de Included + paid.
4. Le backend valide que `STRIPE_SECRET_KEY` et `STRIPE_WEBHOOK_SECRET` sont présents lorsque vous choisissez une posture Stripe — si l'un des deux est manquant, il retourne 400
5. À la première activation d'une posture Stripe, le backend exécute une **configuration idempotente** :
   * Included + paid : initialise le plan inclus (`slug=free`) + un modèle Pro avec un ID de prix vide ; définit `default_plan_id` ; remplit rétroactivement les utilisateurs sans plan
   * Paid only : initialise uniquement le modèle Pro ; ne remplit pas rétroactivement
   * Copie `default_token_quota` sur le plan inclus lors de son initialisation (`0` reste `0`, illimité)
6. Revenir à **No subscriptions** est un **simple changement de drapeau** sans effets secondaires sur les données. Le plan par défaut inclus ne peut pas être supprimé tant que Included + paid est actif.

### 4. Mettez à jour le plan Pro avec votre prix en direct

Après l'activation, mettez à jour le plan Pro pré-configuré pour pointer vers votre prix Stripe en direct :

* **Admin → Billing → Plans → Pro → Edit**
* Collez votre `price_1***` (de l'étape 1) dans `Stripe Price ID`
* Enregistrez

Ou via SQL (si vous préférez un accès direct à la base de données) :

```sql theme={null}
UPDATE billing_plans
SET stripe_price_id = 'price_1***'
WHERE slug = 'pro';
```

### 5. Test de fumée

1. Ouvrez `/settings?tab=billing` en tant qu'utilisateur ordinaire
2. Cliquez sur **Switch to Pro**
3. Stripe Checkout s'ouvre ; complétez avec une vraie carte à faible montant (remboursement après)
4. Le webhook doit se déclencher — vérifiez dans Stripe Dashboard → Webhooks → les événements récents affichent des réponses 2xx
5. Une ligne d'abonnement apparaît dans la table `subscriptions` ; `users.plan_id` bascule à `pro`
6. L'interface affiche maintenant le plan Pro + le bouton « Manage subscription »

## Désactiver la facturation

Basculez le commutateur **Enable Stripe Billing** sur OFF dans Admin → System Settings → Billing.

Lorsque désactivé :

* Tous les points de terminaison `/api/billing/*` retournent 503
* Le point de terminaison webhook retourne 503 (Stripe réessaiera, puis s'affichera dans Dashboard comme défaillant — c'est normal, vous pouvez désactiver le webhook dans Stripe Dashboard à la place si la facturation est définitivement désactivée)
* L'onglet **Plan & Billing** destiné aux utilisateurs disparaît
* Le groupe de navigation Admin → Billing est masqué
* La chaîne de quota ignore le niveau de plan et revient directement à `default_token_quota`

**Les données sont préservées** : les lignes `subscriptions`, `billing_plans` et `users.plan_id` existantes restent inchangées. La réactivation reprend à partir du même état sans migration.

## Référence de calcul — mathématiques des quotas et des jetons

Ceci est la référence faisant autorité pour chaque règle numérique qui décide ce qu'un utilisateur est autorisé à consommer, quand son compteur se réinitialise, et comment la chaîne de résolution se compose. Lisez ceci avant de modifier la tarification, d'ajuster les quotas, de créer des tableaux de bord d'utilisation, ou de planifier les travaux v2/v3. Les règles futures non encore déployées sont documentées dans leur emplacement réservé afin que les contributeurs sachent où s'intègre la nouvelle logique.

### Glossaire

| Variable                              | Stockage                       | Sémantique                                                              | Plage                          |
| ------------------------------------- | ------------------------------ | ----------------------------------------------------------------------- | ------------------------------ |
| `users.token_quota`                   | par utilisateur (remplacement) | Remplacement à trois états ; voir la sémantique ci-dessous              | `NULL`, `0`, ou entier positif |
| `users.tokens_used_this_period`       | par utilisateur (compteur)     | Tokens cumulatifs depuis la dernière réinitialisation                   | entier non-négatif             |
| `users.quota_reset_at`                | par utilisateur (ancre)        | Reflète `Subscription.current_period_end` pour les utilisateurs payants | timestamp                      |
| `users.plan_id`                       | par utilisateur (FK)           | Plan actif                                                              | FK `billing_plans.id`          |
| `billing_plans.monthly_token_quota`   | par plan                       | Limite maximale pour les utilisateurs de ce plan                        | entier non-négatif             |
| `system_settings.default_token_quota` | singleton                      | Secours défensif quand aucun plan ne s'applique                         | entier non-négatif             |
| `system_settings.default_plan_id`     | singleton                      | Pointeur de plan gratuit pour les utilisateurs nouveaux/non assignés    | FK ou `NULL`                   |
| `system_settings.billing_enabled`     | singleton                      | Commutateur maître — contrôle l'étape 2 de la chaîne                    | booléen                        |

### Ce qui compte comme un token

La consommation de tokens est comptabilisée au niveau de l'appel LLM, provenant de l'objet `usage` de LiteLLM sur chaque completion.

* **Comptabilisé** : tokens de prompt + tokens de completion sur chaque appel de modèle
* **Comptabilisé** : chaque aller-retour dans un flux d'agent multi-étapes / tool-use (chaque appel de modèle est son propre débit)
* **Comptabilisé** : demandes d'embedding (ingestion KB, scoring de récupération)
* **Non comptabilisé** : entrée préparée mais jamais envoyée à un modèle (par exemple, fichiers téléchargés que l'utilisateur abandonne)
* **Non comptabilisé** : demandes qui échouent avant d'atteindre le fournisseur (erreur d'authentification, pré-vérification de limite de débit)
* **Entrée en cache** : comptabilisée au prix complet en v1 (aucune remise de cache du fournisseur n'est affichée). v2 peut créditer les tokens de prompt en cache séparément.

### Sémantique de remplacement à trois états

`users.token_quota` est le remplacement administratif par utilisateur. Il porte trois significations dans une seule colonne :

| Valeur  | Signification                         | Cas d'usage                                                                                   |
| ------- | ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `NULL`  | Non défini — déférer au plan / défaut | État par défaut pour tous les utilisateurs normaux                                            |
| `0`     | Illimité                              | Comptes administrateur / internes ; « cadeau VIP »                                            |
| `N > 0` | Limite stricte à `N`                  | Bloquer un contrevenant sans annuler son abonnement payant ; allocation d'entreprise prépayée |

Le remplacement l'emporte toujours sur le plan et le défaut. Il existe pour permettre aux administrateurs d'épingler des utilisateurs individuels au-dessus ou au-dessous de leur niveau de plan sans toucher à Stripe.

### Chaîne de résolution des quotas — v1 (actuelle)

Pour toute demande authentifiée, le plafond est calculé de haut en bas — **la première correspondance gagne** :

```
1. users.token_quota        ── NULL? skip. 0? unlimited. N>0? cap at N.
2. users.plan.monthly_token_quota   ── only when access_model is freemium or paid_only
3. paid_only + no plan      ── unentitled (stop; do not fall through)
4. system_settings.default_token_quota  ── used when access_model is off
5. unlimited                ── last resort if everything above is unset
```

L'étape 4 est le plafond au niveau de l'instance pour **Aucun abonnement**. Sous **Payant uniquement**, un utilisateur sans plan n'a pas de droits (le chat retourne 402) même si `default_token_quota` est 0.

### Réinitialisation de la période

* Pour les utilisateurs payants, `quota_reset_at` reflète `Subscription.current_period_end`. Le gestionnaire webhook `invoice.payment_succeeded` définit `tokens_used_this_period = 0` et avance `quota_reset_at` à la fin de la nouvelle période à chaque renouvellement réussi.
* Pour les utilisateurs Free (sans abonnement Stripe), un cron horaire réinitialise `tokens_used_this_period` à 0 à la limite d'un mois calendaire ancrée à la date d'attribution du plan.
* Les changements de plan en cours de période **ne** réinitialisent **pas** le compteur — seuls les renouvellements le font. Cela prévient les exploits de recyclage de quota (« s'abonner → utiliser le quota Pro → annuler → s'abonner à nouveau »).

### Application de la limite en cours de flux

* Vérification préalable à l'entrée de l'appel de chat : chemin le moins coûteux, bloque les demandes que l'utilisateur ne peut pas se permettre de démarrer.
* Pendant le streaming, le nombre de tokens en cours est réévalué à chaque chunk. Le dépassement du plafond ferme le flux avec un **cadre terminateur structuré**, et non une erreur réseau.
* Le frontend interprète le terminateur et affiche `<QuotaExceededDialog>` avec un lien profond vers `/settings?tab=billing`.
* Les réponses non-streaming retournent HTTP `402` avec le corps `{ code: "QUOTA_EXCEEDED", reset_at, upgrade_url }`.

### Fallback avec facturation désactivée

Quand `system_settings.billing_enabled = FALSE` :

* L'étape 2 de la chaîne est ignorée — la chaîne se réduit à `override → default → unlimited`.
* `/api/billing/*` et `/api/webhooks/stripe` retournent `503`.
* L'onglet utilisateur `Plan & Billing` et le groupe de navigation Admin → Billing sont masqués.
* Toutes les données de facturation (abonnements, plans, `users.plan_id`) sont conservées — la réactivation reprend à partir du même état sans migration.

### Réservé : chaîne de quota v2 — Sièges d'équipe

<Note>Pas encore livré. Documenté ici pour que le travail v2 ait un point de destination connu.</Note>

Quand le plan Team sera lancé :

* `Subscription.quantity` porte le nombre de sièges (natif Stripe).
* Le plan effectif d'un utilisateur se résout via l'appartenance à l'équipe avant de revenir à son plan personnel :
  ```
  effective_plan = team.plan if team_member(user) else user.plan
  ```
* Le quota est **par siège** (chaque membre de l'équipe obtient un `monthly_token_quota` complet), et non un pool partagé. Les pools partagés créent un épuisement au premier arrivé, premier servi et sont contraires aux intérêts des clients.
* La sémantique des remplacements reste inchangée — les administrateurs d'équipe peuvent toujours plafonner fortement les membres individuels via `users.token_quota = N`, qui se situe au-dessus du plan d'équipe dans la chaîne.

### Réservé : chaîne de quota v3 — allocation native Org (sans Stripe)

<Note>Pas encore livré. Réservé pour les déploiements sur site / entreprise qui allouent le quota en interne sans payer Stripe par utilisateur.</Note>

* Nouvelle table `org_quota_allocations(user_id, monthly_token_quota, org_id)` distribue un budget parent entre les membres.
* Les allocations sont **par utilisateur, pas un pool partagé** — chaque membre a un SLA individuel clair.
* Chaîne mise à jour :
  ```
  override → max(plan_quota, org_allocation) → default → unlimited
  ```
* `max()`, pas `sum()`. Un utilisateur Pro payant n'obtient jamais moins que ce qu'il a payé, même si son administrateur Org définit une allocation basse. Le quota payé via Stripe est sacrosaint.

### Réservé : solde de crédits à l'usage (dimension v3 séparée)

<Note>Pas encore livré. Un axe distinct de la chaîne ci-dessus — les crédits sont un rechargement unique, pas un niveau d'abonnement.</Note>

* Nouvelle table `user_credits(user_id, balance_cents, currency)` — financée via Stripe Checkout `mode='payment'`.
* Ordre de consommation : **quota d'abonnement en premier**, puis solde de crédits (la décrémentation des crédits ne commence qu'après l'épuisement de l'abonnement).
* Le solde de crédits n'est pas remboursable (standard industriel pour les prépayés).
* L'interface expose les deux barres : `Quota d'abonnement : 4,2M / 5M utilisés` + `Crédits : 7,40 $ restants`.

***

### Valeurs par défaut

Valeurs par défaut au moment du déploiement — toutes modifiables après installation sauf indication contraire.

| Variable                                           | Défaut                                                         | Modifiable via                                        |
| -------------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------- |
| `system_settings.access_model`                     | `off`                                                          | Admin → System Settings → Access and billing          |
| `billing_plans.monthly_token_quota` (included)     | copiée depuis `default_token_quota` (`0` = illimité)           | Admin → System Settings → Default Monthly Token Quota |
| `billing_plans.monthly_token_quota` (Pro template) | `5,000,000`                                                    | Admin → Billing → Plans → Pro → Edit                  |
| `system_settings.default_token_quota`              | `0` (illimité ; synchronisé avec le plan included en freemium) | Admin → System Settings → Default Monthly Token Quota |
| `system_settings.billing_enabled`                  | `FALSE` (dérivé de `access_model != off`)                      | Admin → System Settings → Access and billing          |
| Pro list price                                     | `$20.00 USD / month`                                           | Stripe Dashboard (price object)                       |
| Stripe webhook events subscribed                   | 6                                                              | Stripe Dashboard → Webhooks                           |
| Stripe price cache TTL                             | `5 minutes`                                                    | hardcoded in `stripe_client.py`                       |
| Subscription lifecycle cron                        | hourly                                                         | APScheduler in `web/main.py`                          |
| Free-tier reset cron                               | hourly (calendar-month boundary)                               | APScheduler in `web/main.py`                          |

## Modèle de tarification

V1 est un abonnement forfaitaire. Gratuit + Pro, facturation mensuelle, USD uniquement.

**Hors du champ d'application pour v1** (intentionnel, reporté à la feuille de route) :

* Plan d'équipe (sièges Stripe / `subscription.quantity`)
* Facturation annuelle
* Présentation multi-devises
* Coupons / codes promotionnels
* Gestion des taxes (intégration Stripe Tax — nécessite un examen de conformité distinct)
* Mesure basée sur l'utilisation / frais de dépassement
* Solde de crédit à l'usage (recharge unique)

Consultez la [feuille de route](/roadmap) pour connaître les prochaines étapes prévues.

## Dépannage

<Accordion title="Le bouton bascule ne s'active pas — erreur 400">
  Le point de terminaison d'activation nécessite que `STRIPE_SECRET_KEY` et `STRIPE_WEBHOOK_SECRET` soient définis. Confirmez qu'ils sont présents dans `.env` et que le backend a été redémarré après la modification.
</Accordion>

<Accordion title="Le webhook retourne 503">
  Soit la facturation est désactivée (le bouton bascule est OFF), soit la vérification de la signature de la requête a échoué (clé `STRIPE_WEBHOOK_SECRET` non concordante). Consultez Stripe Dashboard → Webhooks → événements récents pour voir le corps d'erreur réel.
</Accordion>

<Accordion title="L'utilisateur s'est abonné mais voit toujours le plan gratuit">
  Le webhook `checkout.session.completed` n'a pas atteint votre backend. Vérifiez que l'URL du point de terminaison dans Stripe Dashboard correspond exactement à `<your-domain>/api/webhooks/stripe`, y compris le chemin de fin. Consultez les livraisons récentes du webhook pour les défaillances.
</Accordion>

<Accordion title="L'utilisateur Pro voit l'ID de prix en mode test">
  La migration de seed écrit un ID de prix en mode test. Après l'activation de la facturation en production, mettez à jour le plan Pro pour utiliser votre `price_1***` en direct via Admin → Billing → Plans → Pro → Edit, ou via une UPDATE SQL directe.
</Accordion>

<Accordion title="Les reçus sont marqués avec les paramètres par défaut de Stripe">
  Configurez votre image de marque commerciale dans Stripe Dashboard → Settings → Branding. Ajoutez votre logo, le nom de votre entreprise (par exemple « FIM Labs Pte. Ltd. ») et votre adresse. Stripe applique ces paramètres à tous les reçus et factures générés automatiquement.
</Accordion>

<Accordion title="Pertes de change sur les versements">
  Si la devise par défaut de votre compte Stripe diffère de votre devise de facturation, Stripe convertit à chaque versement (écart de 1,5-2 %). Ajoutez une devise de règlement correspondante sous Settings → Bank accounts and currencies, attachez un compte bancaire dans la même devise, et Stripe acheminera les paiements dans la même devise sans conversion.
</Accordion>
