Vue d'ensemble de la facturation
Architecture de facturation et modèle d'exécution pour les abonnements et les paiements uniques.
Vue d'ensemble
La facturation Yayaw utilise une architecture hybride :
- Plugin Better Auth Stripe pour les abonnements récurrents.
- Flux de webhook Stripe personnalisé pour les paiements uniques (droit à vie).
- Configuration d'exécution répartie entre les valeurs d'environnement et les métadonnées de produits gérées en base de données.
Source de vérité
- Variables d'environnement :
- secrets Stripe
- valeurs Stripe
price_id
- Base de données (
system_settings+ tables de produits de facturation) :- période de grâce dans
system_settings - limites de fonctionnalités des plans dans
billing_plans - métadonnées du catalogue produit et état d'activation
billing_emaild'organisation etstripe_customer_id- droits de paiement unique, grants durables d'accès au code et événements d'idempotence des webhooks
- période de grâce dans
Yayaw est la source de vérité de l'email de contact de facturation. L'email du Stripe Customer est synchronisé depuis l'email de facturation de l'organisation, avec repli sur l'email du compte de l'acteur du checkout lorsque l'organisation n'a pas d'email de facturation explicite. Les détails du Stripe Customer ne sont jamais utilisés pour modifier l'email de connexion de l'utilisateur.
Modèle produit V1 actuel
- Produits d'abonnement :
pro_monthlypro_yearlybusiness_monthlybusiness_yearly
- Produit de paiement unique :
pro_lifetime
Règles de priorité à l'exécution
- Si l'organisation possède un droit
pro_lifetimeactif, l'état de facturation est considéré comme actif et le plan effectif estpro. - Sinon, l'état de facturation suit le statut d'abonnement Stripe et les règles de période de grâce.
- L'application des sièges continue de s'appliquer avec la limite de sièges du plan effectif.
Modèle UX de facturation d'organisation (V2.3)
L'UX de facturation d'organisation est répartie sur trois surfaces du tableau de bord :
/dashboard/organization/billing(résumé du plan actif + statut/sièges + activité)/dashboard/organization/plans(sélection du plan et actions de checkout)/dashboard/organization/code-access(livrables de code source achetés)
Ces surfaces sont pilotées par un view-model serveur dédié :
getOrganizationBillingPageModel(organizationId)overview.activeOfferest résolu côté serveur (free,pro_subscription,business_subscription,pro_lifetime)- les états des CTA produit sont résolus côté serveur (
enabled/disabled + reason) - les cartes produit incluent les miroirs de prix Stripe, les intervalles de facturation, les limites de plan, les fonctionnalités marketing et la mise en évidence de l'offre active
- le client de checkout n'exécute que les types d'action déclarés dans le
view-model (
subscription_checkout,one_time_checkout,billing_portal) - l'action du portail de facturation Stripe reste désactivée jusqu'à ce que l'organisation ait un client Stripe enregistré, ce qui évite d'exposer une action de portail avant le premier checkout réussi
- les réglages d'organisation exposent une carte de contact de facturation; sa sauvegarde met à jour l'email de facturation stocké et synchronise tout Stripe Customer existant
- les requêtes de checkout transmettent la locale active à Stripe afin que Checkout et les emails de facturation de suivi puissent utiliser le bon contexte de locale
Cela évite la dérive des règles métier entre serveur et client.
Accès au code acheté
L'accès au code acheté est dérivé côté serveur à partir de l'autorisation et de l'état de facturation, avec des grants durables persistés quand Stripe confirme un achat :
- L'accès à la route nécessite
code-access:readvia le moteur RBAC existant. - Le seed et la migration accordent cette ressource via les rôles
d'organisation :
- Organization Member :
code-access:list,read - Organization Manager/Admin/Super Admin :
code-access:manage
- Organization Member :
- Le checkout Stripe de paiement unique crée ou met à jour une ligne
billing_code_access_grantsindexée par session de checkout et accorde aussi le droitpro_lifetime. - Les callbacks Better Auth Stripe d'abonnement créent, rafraîchissent, expirent ou révoquent le grant d'organisation indexé par Stripe subscription ID.
- La page se déverrouille lorsque l'utilisateur possède
code-access:readet soit un état de facturation payant actif, soit un grant durable actif. La facturation en période de grâce continue de déverrouiller les plans non gratuits, tandis que les états inactifs, annulés, restreints, expirés ou révoqués gardent les livrables verrouillés.
Les grants d'accès au code sont des enregistrements d'achat à portée organisation, pas des permissions par utilisateur. L'accès utilisateur reste contrôlé par la ressource authz et les rôles de groupe de l'organisation.
L'accès au dépôt GitHub est provisionné séparément via
code_access_github_accounts. Un utilisateur connecté avec code-access:read
et un grant d'organisation actif peut soumettre un nom d'utilisateur GitHub
depuis /dashboard/organization/code-access. Yayaw invite ensuite ce compte sur
le dépôt privé configuré avec l'accès en lecture seule pull. Les identifiants
GitHub App sont préférés pour le provisionnement en production ; un fallback par
token existe pour les environnements locaux ou de staging.
Les réglages non secrets d'accès au dépôt GitHub sont gérés depuis
/dashboard/admin/billing-settings :
- activé/désactivé
- nom complet du dépôt
- GitHub App ID
- GitHub App installation ID
Les variables d'environnement restent disponibles comme fallbacks pour ces valeurs non secrètes, mais les secrets GitHub restent toujours dans les variables d'environnement. Voir Configuration de l'environnement de déploiement pour le workflow complet de création de GitHub App et de récupération des variables d'environnement.
Le workflow de grants n'est volontairement pas encore exposé via MCP, car les grants sont dérivés d'événements de facturation plutôt que d'enregistrements gérés par opérateur. Les invitations au dépôt GitHub ont désormais un état opérationnel et devraient être exposées par des outils de plan de contrôle (control plane) lorsque les workflows retry/revoke/reconcile deviendront des actions opérateur.
Configurez les URLs de livrables avec :
BILLING_CODE_ACCESS_REPOSITORY_URLBILLING_CODE_ACCESS_DOWNLOAD_URLBILLING_CODE_ACCESS_DOCUMENTATION_URLBILLING_CODE_ACCESS_SUPPORT_URL
Configurez les secrets GitHub avec :
BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEYBILLING_CODE_ACCESS_GITHUB_TOKEN(fallback local/staging optionnel)
Fallbacks GitHub non secrets optionnels :
BILLING_CODE_ACCESS_GITHUB_REPOSITORYBILLING_CODE_ACCESS_GITHUB_APP_IDBILLING_CODE_ACCESS_GITHUB_APP_INSTALLATION_ID
Les redirections de succès de checkout pointent vers
/dashboard/organization/code-access afin que le client arrive sur la surface
d'accès après confirmation de l'achat.
Emails transactionnels de facturation
Les webhooks de facturation envoient des emails transactionnels via des modèles
système gérés en base de données lorsque RESEND_API_KEY et des modèles actifs
sont disponibles. Une configuration email manquante ou des modèles manquants
sont journalisés et ne font pas échouer le traitement du webhook.
Slugs de modèles de facturation intégrés :
billing-purchase-confirmation: envoyé après un checkout d'abonnement réussi et après un checkout à vie de paiement unique réussi.billing-subscription-updated: envoyé lorsqu'une annulation d'abonnement est programmée ou lorsque Stripe confirme la suppression d'un abonnement.billing-invoice-available: envoyé après l'événement Stripeinvoice.paid, lorsqu'une facture payée peut être liée depuis un e-mail Yayaw.
Les modèles de facturation déclarent des variables d'achat détaillées, notamment :
billing.productName,billing.planName,billing.productTypebilling.amountFormatted,billing.currency,billing.billingIntervalbilling.optionsSummary,billing.lineItemsSummary,billing.discountSummarybilling.status,billing.periodStart,billing.periodEndbilling.checkoutSessionId,billing.stripeSubscriptionId,billing.stripePaymentIntentIdbilling.invoiceId,billing.invoiceNumber,billing.invoiceUrl,billing.invoicePdfUrl,billing.billingReasonbilling.manageUrl,billing.accessUrl,billing.deliverablesSummary
Les achats en paiement unique envoient d'abord à l'acteur du checkout, puis à
l'email de checkout Stripe, puis au premier propriétaire/admin/manager fallback
de l'organisation. Les emails d'achat d'abonnement utilisent d'abord la métadonnée
userId du checkout Better Auth Stripe, puis l'email de checkout Stripe, puis le
même fallback d'organisation.
Les emails de facture résolvent l'organisation depuis les métadonnées de facture
d'abord, les métadonnées d'abonnement ensuite, puis les lignes locales
subscription/customer en dernier. Le Checkout one-time active la création de
facture Stripe et stocke les métadonnées d'organisation, acteur, produit, plan
et locale sur les factures générées. Si les reçus ou e-mails de facture payée
natifs Stripe sont activés, les clients peuvent recevoir à la fois l'e-mail
Stripe et l'e-mail Yayaw pour la même facture.
Périmètre de la timeline d'activité
La timeline expose volontairement l'activité interne de facturation, pas un registre complet des factures Stripe :
- Instantané d'abonnement actuel
- Droits d'organisation (
billing_entitlements) - Grants d'accès au code (
billing_code_access_grants) - Enregistrements d'accès au dépôt GitHub (
code_access_github_accounts) - Événements de webhook de paiement unique (
billing_webhook_events, providerstripe-one-time)
Les éléments de webhook de paiement unique sont filtrés par organizationId à
partir des métadonnées persistées de payload Stripe.
Règles de sécurité
- Le checkout de paiement unique est bloqué lorsqu'une organisation possède déjà un abonnement actif Stripe-backed non annulé avec un Stripe subscription ID enregistré.
- Le checkout de paiement unique crée ou réutilise le Stripe Customer de
l'organisation et transmet
customerà Stripe Checkout. Quand le Customer a un email valide, Checkout préremplit et verrouille le champ e-mail. - Le traitement des webhooks de paiement unique est idempotent via des event IDs persistés.
- Les upserts de grants d'accès au code sont idempotents par session de checkout Stripe ou Stripe subscription ID.
- Les demandes d'accès au dépôt GitHub sont idempotentes par organisation,
utilisateur et dépôt configuré, et n'accordent que l'accès en lecture seule
pull. - Les valeurs Stripe
price_idmanquantes marquent les produits comme non checkoutables au lieu de faire planter l'exécution.