Finances & Abonnements
La section Finances repond a une question essentielle pour la perennite de BeePass : est-ce que la plateforme est economiquement viable ? Elle permet de suivre les revenus des abonnements, de configurer les plans tarifaires et de mettre en regard les couts d'infrastructure -- le tout depuis une interface unique.
Elle regroupe trois volets : le suivi des commandes Stripe, la configuration des plans d'abonnement et le suivi des couts d'infrastructure.
Architecture Stripe
BeePass delegue entierement le paiement a Stripe. Concretement, aucune donnee de carte bancaire ne transite par nos serveurs -- l'eleveur est redirige vers une page Stripe securisee pour payer, puis renvoye sur BeePass. Ce choix simplifie drastiquement la conformite PCI (les normes de securite des paiements par carte).
BeePass utilise Stripe Checkout (mode redirect) + Customer Portal pour la gestion des paiements. Aucune donnee de carte n'est stockee cote serveur (conformite PCI 100% Stripe).
[Eleveur] -> BillsTab (onglet Facturation)
|
|-- GET /api/billing/subscription -> Stripe API + profiles DB
|-- POST /api/billing/checkout -> Stripe Checkout Session -> redirect
|-- POST /api/billing/portal -> Stripe Customer Portal -> redirect
|-- GET /api/billing/invoices -> Stripe invoices
|
[Public] -> GET /api/plans -> stripe_plans DB (cache 5min, no auth)
|
[Admin] -> GET /api/admin/plans -> stripe_plans DB (tous champs + Stripe IDs)
|-- PUT /api/admin/plans -> update config + Stripe Price create/archive
|-- POST /api/admin/plans/sync -> migration env vars -> DB
|-- GET /api/admin/invoices -> factures Stripe + customer details
|-- GET /api/admin/revenue -> revenus mensuels agreges
|
[Stripe] -> POST /api/billing/webhook -> profiles DB (service_role, bypass RLS)
Commandes Stripe
Tableau des factures
Le tableau (/backoffice/orders) centralise toutes les factures emises par Stripe. C'est l'endroit ou aller quand un eleveur demande "Ou est ma facture ?" ou pour verifier qu'un paiement a bien ete traite.
Il affiche l'ensemble des factures via un datatable TanStack React Table :
| Colonne | Description |
|---|---|
| N. facture | Identifiant Stripe de la facture |
| Client | Nom de l'utilisateur associe (enrichi via priceToPlanAsync()) |
| Plan | Nom du plan d'abonnement facture (resolu depuis stripe_plans DB) |
| Montant | Montant en EUR (TTC) |
| Statut | paid, open, draft, void |
| Periode | Mensuel ou annuel |
| Date | Date d'emission de la facture |
Filtres et actions
| Fonctionnalite | Description |
|---|---|
| Filtre par statut | Selection rapide : Payees, En attente, Brouillon, Annulees |
| Recherche | Par nom de client |
| Export CSV | Telechargement du tableau filtre |
| Lien Stripe | Clic sur une facture pour l'ouvrir dans le Stripe Dashboard |
| Telecharger PDF | Telechargement de la facture PDF via l'API Stripe |
Indicateurs de revenus
Deux cartes synthetisent les revenus en haut de page pour un apercu immediat :
| Indicateur | Description | Source |
|---|---|---|
| Revenus du mois | Total des factures payees sur le mois en cours | /api/admin/revenue |
| Montant moyen | Montant moyen par facture payee | /api/admin/revenue |
Les donnees financieres sont recuperees depuis l'API Stripe avec un polling de 5 minutes (useAutoRefresh avec interval: 300000 + Realtime sur profiles). Le webhook Stripe met a jour les colonnes billing dans profiles en temps reel, ce qui signifie que le plan d'un eleveur change instantanement apres paiement.
Gestion des plans
Pourquoi configurer les plans en base de donnees plutot qu'en dur dans le code ? Pour pouvoir ajuster les prix, les periodes d'essai ou les fonctionnalites sans avoir a redeployer l'application. Tout est modifiable depuis l'interface admin.
BeePass propose 4 plans d'abonnement configures dans la table stripe_plans (DB-driven) :
| Plan | Slug | Mensuel | Annuel | Essai | Populaire | Checkout |
|---|---|---|---|---|---|---|
| Decouverte | discovery | 0 EUR | 0 EUR | -- | Non | -- |
| Eleveur | breeder | 12 EUR | 115 EUR | 30 jours | Non | Stripe |
| Selectionneur | selector | 39 EUR | 374 EUR | 30 jours | Oui | Stripe |
| Groupe de selection | program | Sur devis | Sur devis | -- | Non | Contact |
- Reduction annuelle : -20% (Eleveur 12 EUR/mois -> 115 EUR/an, Selectionneur 39 EUR/mois -> 374 EUR/an)
- Le plan
programn'a pas de checkout Stripe -- le CTA redirige vers le chat pour un contact commercial - Le plan
discoveryest gratuit pour toujours, sans checkout
Schema DB stripe_plans
| Colonne | Type | Description |
|---|---|---|
id | TEXT PK | Slug plan : discovery, breeder, selector, program |
is_active | BOOLEAN NOT NULL | Plan visible sur Pricing et BillsTab (defaut TRUE) |
is_popular | BOOLEAN NOT NULL | Badge "Recommande" -- exclusif (un seul plan) |
monthly_price_cents | INTEGER | Prix mensuel en centimes (NULL = gratuit/devis) |
yearly_price_cents | INTEGER | Prix annuel en centimes (NULL = gratuit/devis) |
trial_days | INTEGER NOT NULL | Duree essai gratuit en jours (defaut 0) |
stripe_product_id | TEXT | Stripe Product ID (cree au 1er sync ou changement prix) |
monthly_stripe_price_id | TEXT | Stripe Price ID actif mensuel |
yearly_stripe_price_id | TEXT | Stripe Price ID actif annuel |
features | JSONB | Array cles i18n des fonctionnalites du plan |
feature_labels | JSONB | Object cle -> texte custom (features admin) |
sort_order | INTEGER NOT NULL | Ordre d'affichage (defaut 0) |
updated_at | TIMESTAMPTZ NOT NULL | Auto via trigger |
created_at | TIMESTAMPTZ NOT NULL | Date de creation |
RLS : SELECT ouvert a tous (pricing page publique). Pas de policy INSERT/UPDATE/DELETE -- service_role uniquement (seuls les admins via l'API serveur peuvent modifier les plans).
Configuration par plan
Pour chaque plan, l'administrateur peut modifier depuis /backoffice/plans :
| Parametre | Description | Editable |
|---|---|---|
| Prix mensuel | Montant en EUR facture chaque mois | Breeder, Selector |
| Prix annuel | Montant en EUR facture chaque annee | Breeder, Selector |
| Jours d'essai | Duree de la periode d'essai gratuit (0 = pas d'essai) | Breeder, Selector |
| Actif | Plan visible sur la page tarifs publique | Tous |
| Popularite | Badge "Recommande" -- exclusif (un seul plan a la fois) | Tous |
| Features | Liste des fonctionnalites affichees (cles i18n) | Tous |
Les prix des plans Discovery et Program ne sont pas editables (gratuit / sur devis).
Les prix Stripe sont immutables -- c'est une contrainte imposee par Stripe. Une fois cree, un prix ne peut pas etre modifie. Chaque modification de prix declanche : creation d'un nouveau Stripe Price -> archivage de l'ancien. Les abonnements existants conservent leur tarif jusqu'au prochain renouvellement, ce qui signifie qu'un changement de prix n'affecte pas retroactivement les clients deja abonnes.
Flux de modification de prix
Ce schema montre ce qui se passe en coulisses quand un admin change un prix. L'operation est transparente pour l'admin, mais implique plusieurs appels API Stripe.
1. Admin modifie le prix mensuel Eleveur : 12 EUR -> 15 EUR
2. PUT /api/admin/plans { id: "breeder", monthly_price_cents: 1500 }
3. stripe.products.create() si pas de stripe_product_id (lazy)
4. stripe.prices.create({ unit_amount: 1500, currency: 'eur', recurring: { interval: 'month' } })
5. stripe.prices.update(old_id, { active: false }) -- archive
6. UPDATE stripe_plans SET monthly_stripe_price_id = new_id, monthly_price_cents = 1500
7. invalidatePlanCache() -- cache serveur invalide
8. Page Pricing publique affiche le nouveau prix au prochain fetch
Statistiques par plan
Chaque plan affiche dans l'interface admin des informations pour evaluer sa performance :
- Nombre d'abonnes actifs (badge compteur)
- MRR (Monthly Recurring Revenue, c'est-a-dire le revenu recurrent mensuel) pour ce plan
- Stripe Price IDs (copiables dans le clipboard, utiles pour le debugging Stripe)
- Features editables en inline
- Historique des modifications (table
stripe_plan_history)
Table stripe_plan_history
Chaque modification de plan est enregistree pour garder une trace complete. Cela permet de repondre a des questions comme "Quand le prix du plan Eleveur a-t-il ete augmente ?"
| Colonne | Type | Description |
|---|---|---|
plan_id | TEXT FK | Reference vers stripe_plans(id) CASCADE |
change_type | TEXT | price_change, trial_change, status_change, popular_change, features_change |
old_values | JSONB | Valeurs avant modification |
new_values | JSONB | Valeurs apres modification |
created_at | TIMESTAMPTZ | Horodatage |
Webhook Stripe
Le webhook est le mecanisme par lequel Stripe informe BeePass qu'un evenement s'est produit (paiement reussi, abonnement annule, etc.). Sans webhook, BeePass ne saurait pas quand mettre a jour le plan d'un utilisateur.
Le webhook (POST /api/billing/webhook) gere 4 types d'evenements :
| Event Stripe | Action | Notification |
|---|---|---|
checkout.session.completed | Retrieve subscription -> update profiles (plan, status, period_end, trial_end) | Email admin Brevo |
customer.subscription.updated | Sync plan (via priceId), status, period_end, trial_end | Email admin Brevo |
customer.subscription.deleted | Reset a discovery + status canceled + clear stripe IDs | Email admin Brevo |
invoice.payment_failed | Set status past_due (lookup par stripe_customer_id) | Email admin Brevo |
Securite webhook :
- Verification signature Stripe (
stripe-signatureheader +STRIPE_WEBHOOK_SECRET) -- garantit que la requete vient bien de Stripe et n'a pas ete falsifiee - Runtime
nodejs(pas Edge -- besoin du raw body pour verification signature) - Body lu via
request.text()(pas.json()) - DB writes via
service_roleclient (bypass RLS)
Resolution des plans : Le webhook utilise priceToPlanAsync() pour resoudre le plan a partir du price ID. Cette fonction verifie d'abord la table stripe_plans (DB), puis les variables d'environnement en fallback pour les anciens price IDs qui existaient avant la migration vers la table DB.
Notifications admin : Chaque event Stripe declenche un email Brevo aux admins via notifyAdmins('subscription_changes') (fire-and-forget). Template Brevo ID=4. Filtre par notification_prefs.subscription_changes -- les admins peuvent choisir de ne pas recevoir ces notifications.
Depuis EMAIL v2.7.0, les webhooks Stripe declenchent aussi des emails transactionnels aux eleveurs :
checkout.session.completed→ email confirmation de paiement (template multilingue Brevo)invoice.payment_failed→ email echec de paiement (template multilingue Brevo)
Ces emails sont envoyes en parallele avec la notification admin (notifyAdmins('subscription_changes')).
Flux Checkout complet
Voici le parcours complet d'un eleveur qui s'abonne, du clic sur "Passer a ce plan" jusqu'a la confirmation :
1. [BillsTab] Toggle monthly/yearly + Clic "Passer a ce plan"
2. [Frontend] POST /api/billing/checkout { plan: "breeder", billing_period: "yearly" }
3. [API] loadStripePlans() -> verifie plan actif + prix configure
4. [API] getPlanPriceId("breeder", "yearly") -> Stripe Price ID depuis DB
5. [API] Get/create Stripe Customer -> store stripe_customer_id
6. [API] Create Checkout Session (subscription, trial_days dynamique)
7. [API] Return { url: "https://checkout.stripe.com/..." }
8. [Frontend] window.location.href = url (redirect Stripe)
9. [Stripe] Utilisateur saisit carte -> paiement/trial
10. [Stripe] Webhook -> POST /api/billing/webhook (checkout.session.completed)
11. [Webhook] Update profiles: plan, status, period_end, trial_end
12. [Stripe] Redirect -> /account-settings?checkout=success
13. [BillsTab] Toast succes + fetchSubscription() -> affiche nouveau plan
Couts d'infrastructure
Connaitre ses couts d'infrastructure est indispensable pour evaluer la rentabilite de la plateforme. Cette page permet de maintenir un registre a jour de toutes les depenses, et de les comparer aux revenus affiches sur le tableau de bord.
Cette page (/backoffice/services) permet de suivre les depenses recurrentes et ponctuelles liees a l'hebergement et aux services tiers. Les donnees sont stockees dans la table admin_services.
Schema DB admin_services
| Colonne | Type | Description |
|---|---|---|
name | TEXT NOT NULL | Nom du service |
amount_cents | INTEGER NOT NULL | Montant mensuel en centimes (>= 0) |
is_recurring | BOOLEAN | true = recurrent mensuel, false = ponctuel |
notes | TEXT | Notes complementaires |
created_at | TIMESTAMPTZ | Date de creation |
Services suivis
| Service | Type | Frequence |
|---|---|---|
| Hetzner CCX23 | Serveur principal (4 vCPU / 16 GB) | Mensuel |
| Hetzner CX53 | Serveur production (16 vCPU / 32 GB) | Mensuel |
| Supabase | Base de donnees + Auth | Mensuel |
| Cloudflare | CDN + DNS + WAF | Mensuel |
| Brevo | Emails transactionnels + CRM | Mensuel |
| Domaine beepass.io | Nom de domaine | Annuel |
| Certificats SSL | Via Cloudflare Origin Cert (inclus, 15 ans) | -- |
Gestion des entrees
| Action | Description |
|---|---|
| Ajouter | Nouveau service avec nom, montant, frequence, notes |
| Modifier | Mise a jour du montant ou des notes |
| Supprimer | Retrait d'un service obsolete |
Calcul des totaux
La page calcule automatiquement les couts en ramenant tout a une base comparable :
| Total | Calcul |
|---|---|
| Cout mensuel | Somme des services mensuels + (services annuels / 12) |
| Cout annuel | Somme des services annuels + (services mensuels x 12) |
En croisant les couts d'infrastructure avec les revenus Stripe du tableau de bord, vous pouvez suivre la marge operationnelle de la plateforme en temps reel. Les widgets Revenue Chart et Monthly Earning affichent les revenus bruts Stripe, tandis que cette page affiche les couts -- la difference donne la marge.
Variables d'environnement
| Variable | Scope | Description |
|---|---|---|
STRIPE_SECRET_KEY | Server only | Cle secrete Stripe (sk_live_... ou sk_test_...) |
STRIPE_WEBHOOK_SECRET | Server only | Secret webhook Stripe (whsec_...) -- utilise pour verifier la signature des webhooks |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Client | Cle publique (reserve futur Stripe Elements) |
STRIPE_PRICE_BREEDER | Server only | Price ID legacy Eleveur (fallback pour les abonnements crees avant la migration DB) |
STRIPE_PRICE_SELECTOR | Server only | Price ID legacy Selectionneur (fallback) |
API Routes
| Route | Methode | Auth | Description |
|---|---|---|---|
/api/billing/subscription | GET | Supabase SSR | Plan actuel + moyen de paiement |
/api/billing/checkout | POST | Supabase SSR | Creer Checkout Session (monthly/yearly) |
/api/billing/portal | POST | Supabase SSR | Creer Customer Portal session |
/api/billing/webhook | POST | Signature Stripe | Webhook (4 events) |
/api/billing/invoices | GET | Supabase SSR | Factures eleveur |
/api/plans | GET | Aucune (public) | Plans actifs sans Stripe IDs (cache 5min) |
/api/admin/plans | GET | Cookie HMAC | Tous les plans avec Stripe IDs |
/api/admin/plans | PUT | Cookie HMAC | Modifier plan + Stripe Price create/archive |
/api/admin/plans/sync | POST | Cookie HMAC | Migration env vars -> DB + creation Stripe Prices manquants |
/api/admin/plans/stats | GET | Cookie HMAC | Compteurs abonnes + MRR par plan |
/api/admin/plans/history | GET | Cookie HMAC | Historique modifications pagine |
/api/admin/invoices | GET | Cookie HMAC | Factures Stripe + customer details |
/api/admin/revenue | GET | Cookie HMAC | Revenus mensuels agreges |
Voir aussi : Tableau de bord | Gestion des utilisateurs | Vue d'ensemble