Files
office_translator/docs/PRIX-ABONNEMENTS.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:47 +02:00

117 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prix et abonnements — référence exacte
Ce document décrit **uniquement** le comportement actuel du code (chemins, formules, endpoints). Aucune phrase générique : chaque affirmation correspond à une implémentation précise.
---
## 1. Où sont stockés les données tarifaires
| Rôle | Emplacement | Contenu |
|------|-------------|---------|
| Surcharges (prix modifiables par ladmin) | Fichier **`data/pricing_overrides.json`** à la racine du dépôt backend (répertoire de travail du processus Python) | Pour chaque clé `starter`, `pro`, `business` : `price_monthly`, `price_yearly` (recalculé par le serveur), `stripe_price_id_monthly`, `stripe_price_id_yearly` (optionnel, chaînes vides possibles). |
| Variables denvironnement | Fichier **`.env`** à la racine du backend | Clés `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`, et `STRIPE_PRICE_STARTER_MONTHLY`, `STRIPE_PRICE_STARTER_YEARLY`, idem pour `PRO` et `BUSINESS`. Ladmin peut mettre à jour ce fichier lors dune sauvegarde. |
| Valeurs par défaut du code | **`models/subscription.py`**, dictionnaire **`PLANS`** | Utilisé si une valeur nexiste pas dans `pricing_overrides.json` pour le **prix mensuel** de chaque plan. Les quotas (documents/mois, pages, etc.) et les textes de fonctionnalités viennent **toujours** de `PLANS`, pas du JSON doverride. |
---
## 2. Qui peut modifier les prix
- **Interface admin** : page **Pricing & Stripe** (`frontend/src/app/admin/pricing/page.tsx`).
- **API** : uniquement avec un **jeton administrateur** valide.
- **Lecture** : `GET /api/v1/admin/pricing`
- **Écriture** : `PUT /api/v1/admin/pricing`
- **Création automatique Stripe** : `POST /api/v1/admin/pricing/setup-stripe`
Les utilisateurs non admin **ne** peuvent **pas** modifier les tarifs. Ils ne voient que la liste publique (voir section 5).
---
## 3. Règle de calcul du prix annuel (pas de choix libre)
Le serveur impose une seule règle :
```
prix_annuel = arrondi(prix_mensuel × 12 × 0,8 ; 2 décimales)
```
- **Constante dans le code** : `YEARLY_DISCOUNT_FACTOR = 0.8` dans `services/pricing_config.py`.
- **Interprétation** : le total annuel payé dun coup = **80 %** de ce que coûteraient **12 mois** au tarif mensuel. Cest **équivalent** à une réduction de **20 %** sur ce total « 12 × mensuel ».
- **Constante affichée** : `ANNUAL_DISCOUNT_PERCENT = 20` (même valeur, autre formulation).
**Conséquence** : tu ne définis pas un annuel arbitraire. Tu définis le **mensuel** ; le serveur **recalcule** lannuel et lenregistre dans `pricing_overrides.json` lors dun `PUT`. Un champ `price_yearly` envoyé par un client est **ignoré** pour la logique métier (le serveur normalise).
**Plage autorisée pour le prix mensuel** (forfaits payants `starter` / `pro` / `business`) : **entre 0,01 € et 50 000 €** (validation dans `validate_monthly_price_eur`).
---
## 4. Ordre de résolution pour les prix affichés et le paiement
### 4.1 Montants en euros (mensuel / annuel)
Pour un plan `starter`, `pro` ou `business` :
1. Lire `price_monthly` dans **`data/pricing_overrides.json`** pour ce plan.
2. Si absent, utiliser `price_monthly` dans **`PLANS`** (`models/subscription.py`).
3. Calculer `price_yearly` avec la formule de la section 3.
Pour les plans **gratuit** (`free`) et **entreprise** (`enterprise`), les montants spéciaux (0 ou « sur devis ») viennent de **`PLANS`** ; **pas** de même logique de remise annuelle.
### 4.2 Identifiants Stripe Price (`price_…`)
Pour savoir quel Stripe Price utiliser au checkout :
1. **Dabord** : champs `stripe_price_id_monthly` and `stripe_price_id_yearly` dans **`data/pricing_overrides.json`** pour le plan.
2. **Sinon** : variables denvironnement `STRIPE_PRICE_<PLAN>_MONTHLY` et `STRIPE_PRICE_<PLAN>_YEARLY` (ex. `STRIPE_PRICE_STARTER_MONTHLY`), après **rechargement** du fichier `.env` en mémoire pour le processus (voir `reload_dotenv_from_dotenv_file` dans `services/pricing_config.py`).
Il ny a **plus** de repli sur les `os.getenv` figés dans le dictionnaire `PLANS` au moment de limport du module pour ces identifiants.
### 4.3 Comportement du checkout Stripe (`services/payment_service.py`)
1. Si un **identifiant** `price_…` valide est trouvé pour la période **mensuelle** ou **annuelle** : la session Checkout utilise **ce** Stripe Price (montant défini **dans Stripe**).
2. Sinon : création dune ligne avec **`price_data`** (montant en centimes) calculé à partir des **mêmes** règles euros que laffichage (`get_subscription_line_amount_eur`), **sans** redémarrage du serveur tant que `pricing_overrides.json` est à jour.
---
## 5. Ce que voit le site public (sans être admin)
- **Endpoint HTTP** : `GET /api/v1/auth/plans` (sans authentification).
- **Réponse** : liste des plans avec `price_monthly`, `price_yearly`, `annual_discount_percent`, et les limites (documents, pages, etc.) issues de **`PLANS`**.
- **Métadonnées** : `meta.annual_discount_percent` (20) et `meta.yearly_discount_factor` (0,8).
La page **`/pricing`** du frontend charge cet endpoint et remplace les données statiques de secours si la réponse est valide.
---
## 6. Packs de crédits (hors admin)
Les **prix des packs de crédits** (nombre de crédits, prix en euros) sont définis dans **`models/subscription.py`**, structure **`CREDIT_PACKAGES`**. Ils **ne** sont **pas** modifiables via lécran **Pricing & Stripe** actuel. Ils apparaissent dans `GET /api/v1/auth/plans` sous `credit_packages`.
---
## 7. Bouton « Créer automatiquement » (Stripe)
- **Endpoint** : `POST /api/v1/admin/pricing/setup-stripe`.
- **Condition** : `STRIPE_SECRET_KEY` présente dans lenvironnement et commençant par `sk_`.
- **Effet** : le backend crée ou retrouve des produits Stripe et des **prix mensuels et annuels** en **euros** dont les montants **en centimes** sont calculés à partir des **mêmes** règles euros que ci-dessus (mensuel effectif + annuel = `mensuel × 12 × 0,8`). Les identifiants `price_…` sont enregistrés dans **`.env`** et dans **`data/pricing_overrides.json`**.
---
## 8. Redémarrage du serveur
- **Pas requis** pour que les **montants** (JSON) soient pris en compte : le fichier est lu à chaque requête qui appelle `load_pricing_overrides()`.
- Après modification du **`.env`** par ladmin, le code appelle **`apply_runtime_config_after_admin_write()`** pour recharger les variables dans le processus courant.
En cas de **plusieurs processus** (plusieurs workers), chaque processus recharge les variables lorsquil en a besoin ou après une écriture admin sur le même worker ; le fichier **`data/pricing_overrides.json`** est partagé sur le disque.
---
## 9. Résumé opérationnel (ce que tu fais concrètement)
1. Te connecter en **admin**.
2. Ouvrir **Pricing & Stripe**.
3. Saisir le **prix mensuel** pour Starter, Pro, Business (lannuel affiché est calculé ; il nest pas saisi librement).
4. Optionnel : renseigner les **Price ID** Stripe ou utiliser **Créer automatiquement** si la clé secrète Stripe est configurée.
5. Cliquer sur **Sauvegarder la configuration**.
Les changements sont persistés dans **`data/pricing_overrides.json`** et, le cas échéant, dans **`.env`**. Les pages publiques qui consomment **`GET /api/v1/auth/plans`** reflètent les nouveaux montants sans redémarrage obligatoire du serveur.