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

# Facturation Stripe

> Activez les abonnements Stripe par utilisateur (Gratuit + Pro) avec quotas de jetons et gestion de plan en libre-service.

FIM One intègre un pipeline de facturation Stripe complet contrôlé par un drapeau de fonctionnalité administrateur. Les déploiements privés sans besoin de paiement le laissent désactivé et ne voient jamais l'interface utilisateur. Les opérateurs SaaS basculent un seul paramètre et obtiennent immédiatement la Checkout hébergée, le Portail Client, le cycle de vie des abonnements piloté par webhook et l'application des quotas.

<Note>
  La facturation est **désactivée par défaut**. Les nouvelles installations et les déploiements autonomes existants commencent avec `system_settings.billing_enabled = FALSE`. Aucune interface de facturation n'apparaît tant qu'un administrateur ne l'active pas explicitement.
</Note>

## Ce que vous obtenez

* **Niveaux Free + Pro** avec quotas de tokens mensuels (par défaut : Free 1M / Pro 5M ; tous deux ajustables après activation)
* **Stripe Checkout hébergé** — les utilisateurs passent à la version payante sans que votre code ne touche jamais aux données de carte
* **Portail client** — les utilisateurs mettent à jour les méthodes de paiement, téléchargent les factures, annulent — tout sur l'interface de Stripe
* **Cycle de vie piloté par webhooks** — les abonnements se provisionnent et se renouvellent automatiquement ; les abonnements annulés rétrogradent à Free à la fin de la période
* **Application des quotas** — utilisation des tokens suivie par période ; arrêt 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. Activate in Admin

1. Log in as an admin
2. **Admin → System Settings → Billing**
3. Toggle **Enable Stripe Billing** ON
4. The backend validates that both `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` are present — if either is missing it returns 400 and the toggle stays OFF
5. On first activation only, the backend runs an **idempotent setup**:
   * Seeds Free + Pro plans (skipped if already present)
   * Sets `system_settings.default_plan_id` to the Free plan id
   * Backfills `users.plan_id = free.id` for any user without a plan
   * Syncs `default_token_quota` → Free plan `monthly_token_quota` so the existing admin-controlled global quota carries over
6. Subsequent toggle off/on is a **pure flag flip** with no data side effects

### 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 requête 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 billing_enabled = TRUE
3. system_settings.default_token_quota  ── defensive fallback
4. unlimited                ── last resort if everything above is NULL
```

L'étape 3 est rarement atteinte quand la facturation est activée, car l'activation remplit rétroactivement `users.plan_id` pour chaque utilisateur. Elle existe comme une défense en profondeur pour qu'un plan mal configuré ne lève jamais silencieusement le plafond d'un utilisateur.

### 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 ajustables après installation sauf indication contraire.

| Variable                                   | Défaut                                             | Ajustable via                         |
| ------------------------------------------ | -------------------------------------------------- | ------------------------------------- |
| `billing_plans.monthly_token_quota` (Free) | `1,000,000`                                        | Admin → Billing → Plans → Free → Edit |
| `billing_plans.monthly_token_quota` (Pro)  | `5,000,000`                                        | Admin → Billing → Plans → Pro → Edit  |
| `system_settings.default_token_quota`      | `1,000,000` (synchronisé avec Free à l'activation) | Admin → System Settings → Quotas      |
| `system_settings.billing_enabled`          | `FALSE`                                            | Admin → System Settings → Billing     |
| Prix catalogue Pro                         | `$20.00 USD / mois`                                | Stripe Dashboard (objet price)        |
| Événements webhook Stripe abonnés          | 6                                                  | Stripe Dashboard → Webhooks           |
| TTL cache prix Stripe                      | `5 minutes`                                        | codé en dur dans `stripe_client.py`   |
| Cron cycle de vie abonnement               | toutes les heures                                  | APScheduler dans `web/main.py`        |
| Cron réinitialisation niveau gratuit       | toutes les heures (limite calendaire mensuelle)    | APScheduler dans `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>
