YYayaw

Procédure opérationnelle de test de la facturation

Stratégie de validation manuelle et automatisée pour les abonnements, le checkout de paiement unique, les webhooks et l'application des règles.

Objectif

Cette procédure opérationnelle définit comment valider la facturation de manière sûre avant une release.

Elle combine :

  • tests unitaires (logique pure)
  • vérifications d'intégration scriptées (flux au niveau contrat)
  • validation manuelle des webhooks Stripe en staging

Source de vérité de la configuration

La facturation utilise un modèle de sources hybride :

  1. Les variables d'environnement restent la source de vérité pour les secrets Stripe.
  2. billing_products stocke les prix produit gérés par opérateur, les Stripe price IDs gérés en interne et les métadonnées Stripe Product/Price en miroir.
  3. billing_stripe_coupons et billing_stripe_promotion_codes stockent un miroir en lecture des remises Stripe pour les usages CMS/MCP.
  4. La clé billing.config.v1 de system_settings stocke la configuration globale de facturation non secrète :
    • jours de période de grâce
  5. billing_plans stocke les limites de fonctionnalités des plans :
    • limites d'upload et de stockage média par plan
    • limites de sièges par plan
  6. Le fallback d'exécution est déterministe :
    • un payload DB valide gagne pour les champs non secrets
    • un payload DB manquant/invalide retombe sur les valeurs par défaut d'env
    • les anciens payloads billing.config.v1 avec des réglages de plan embarqués sont normalisés vers la forme actuelle limitée à la période de grâce

Vérifications automatisées

Tests unitaires

Exécutez :

bun test

Ces tests couvrent :

  • mapping du statut Stripe vers le statut produit
  • comportement de la période de grâce
  • calculs de sièges et éligibilité des invitations
  • règles de permission de checkout par rôle et organisation active
  • comportement des indicateurs de facturation (billing-enabled, billing-enforcement-enabled)
  • règles du page-model de facturation d'organisation :
    • résolution de l'offre active (free, pro_subscription, business_subscription, pro_lifetime)
    • raisons de désactivation des CTA
    • agrégation de l'activité interne
  • verrouillage de l'accès au code acheté depuis authz + état de facturation payant actif
  • cycle de vie des grants durables d'accès au code pour les achats en paiement unique et les changements de statut d'abonnement
  • normalisation des noms d'utilisateur GitHub et configuration de provisionnement du dépôt

Vérifications d'intégration scriptées

Exécutez :

bun run test

Le runner de tests exécute tous les scripts dans src/lib/scripts/tests/test-*.ts, notamment :

  • test-billing-checkout-permissions.ts
  • test-billing-webhook-contract.ts
  • test-billing-seat-limits.ts
  • test-billing-one-time-lifetime.ts
  • test-billing-products-admin.ts
  • test-billing-organization-view.ts

Miroir du catalogue Stripe

Lors de la mise à jour des prix produit via admin ou MCP, vérifiez que :

  1. Yayaw crée ou réutilise le Stripe Product attendu.
  2. Le type du Stripe Price correspond au type du produit Yayaw (recurring pour les abonnements, one_time pour le checkout à vie).
  3. Les prix récurrents correspondent à l'intervalle attendu (month ou year).
  4. Un nouveau Stripe Price est créé lorsque le montant, la devise ou l'intervalle change, et le Price précédent est archivé pour les nouveaux checkouts.
  5. Le montant, la devise, le nom Product, l'état actif et l'horodatage de synchronisation en miroir sont persistés.
  6. yayaw_stripe_discounts_sync réplique les coupons et codes promotionnels sans traiter Yayaw comme source de vérité.

Validation manuelle de replay d'événement échoué

Après avoir forcé un échec de webhook de paiement unique en staging, validez le replay :

bun run billing:replay-webhooks

Résultat attendu :

  1. L'événement échoué est réessayé une fois.
  2. Le statut de l'événement passe de failed à processed.
  3. Le droit reste idempotent (aucun effet de bord dupliqué).

Validation Stripe manuelle (staging)

Prérequis

  1. Environnement de staging configuré avec les clés Stripe.
  2. App déployée et accessible.
  3. Stripe CLI installé et authentifié.

Démarrer le forwarding des webhooks

Utilisez Stripe CLI pour transférer les événements d'abonnement vers l'endpoint Better Auth :

stripe listen --forward-to https://<staging-domain>/api/auth/stripe/webhook

Si votre endpoint Better Auth Stripe est routé sous la route auth catch-all, gardez cette cible de forwarding alignée avec votre configuration de plugin.

Transférez les événements de paiement unique vers l'endpoint de webhook personnalisé :

stripe listen --forward-to https://<staging-domain>/api/billing/stripe/webhook

Déclencher les événements clés

Exécutez ces commandes (ou des actions équivalentes dans Dashboard) :

stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed
stripe trigger invoice.paid
stripe trigger customer.subscription.deleted

Valider les résultats attendus

  1. checkout.session.completed met l'abonnement à l'état actif.
  2. invoice.payment_failed place l'organisation en période de grâce.
  3. Après le seuil de grâce, l'organisation devient restreinte pour les actions premium.
  4. invoice.paid restaure l'état actif.
  5. customer.subscription.deleted définit l'état annulé.
  6. La livraison dupliquée de webhook est idempotente (aucun effet de bord dupliqué).
  7. Le checkout.session.completed de paiement unique sur /api/billing/stripe/webhook accorde le droit pro_lifetime.
  8. Le checkout de paiement unique crée aussi une ligne billing_code_access_grants indexée par session de checkout Stripe.
  9. Le checkout d'abonnement crée ou rafraîchit une ligne billing_code_access_grants indexée par Stripe subscription ID.
  10. Une annulation d'abonnement programmée garde le grant actif jusqu'à la fin de période, et la suppression d'abonnement révoque le grant.
  11. Une organisation avec le droit pro_lifetime reste active même si le statut d'abonnement est en défaut.
  12. Les événements de paiement unique échoués peuvent être rejoués avec billing:replay-webhooks et récupérés.
  13. Un checkout réussi redirige vers /dashboard/organization/code-access, où les livrables sont déverrouillés uniquement lorsque code-access:read et l'état de facturation payant actif ou un grant durable actif passent.
  14. Avec le provisionnement GitHub configuré, un utilisateur déverrouillé peut soumettre un nom d'utilisateur GitHub et créer une ligne code_access_github_accounts pour le dépôt configuré.

Validation de la timeline d'activité interne

La liste d'activité de facturation d'organisation n'est volontairement pas un registre financier Stripe complet. Vérifiez que la timeline UI reflète uniquement :

  1. le dernier instantané d'abonnement
  2. les grants de droits
  3. les grants d'accès au code
  4. les enregistrements d'accès au dépôt GitHub
  5. les événements de webhook de paiement unique persistés et bornés à l'organisation active

Validez aussi les surfaces UX séparées :

  1. /dashboard/organization/billing affiche le résumé du plan actif et l'activité
  2. /dashboard/organization/plans héberge les actions de checkout
  3. /dashboard/organization/code-access affiche les livrables achetés après un checkout réussi

Checklist de régression

Avec billing-enabled = false :

  1. Les flux auth existants fonctionnent toujours.
  2. Les pages de réglages d'organisation et de membres fonctionnent toujours.
  3. La navigation ne régresse pas.

Avec billing-enabled = true et billing-enforcement-enabled = false :

  1. L'UI de facturation et les chemins de checkout sont disponibles.
  2. Aucun blocage dur pour les invitations.

Avec les deux indicateurs activés :

  1. Les restrictions de sièges et de statut sont appliquées côté serveur.
  2. Le blocage d'invitation correspond aux tests de contrat.

Dépanner les blocages esbuild / Drizzle

Si des commandes locales sont bloquées sur la génération docs ou Drizzle :

  1. Utilisez les commandes sûres avec timeouts :
    • bun run docs:generate
    • bun run db:generate
    • bun run db:push
  2. Si le timeout persiste, réinstallez les dépendances et relancez :
    • rm -rf node_modules
    • bun install

Gate de release

Avant merge :

bun run check
bunx tsc --noEmit
bun run test
bun run docs:generate
bun run docs:check-links
bun run docs:check-translations
bun run docs:llm:check
bun run build