Audits · septembre 2026Audit — charge billing (visiteurs et orgs)

Audit — charge billing (visiteurs et orgs)

Lecture seule sur le code, plus le trafic et une journee de facture Vercel. Ce document et trois items de backlog sont la sortie. Perimetre : le ledger de credits, les crons daily-bonus et reset-credits, les plafonds…

Lecture seule sur le code, plus le trafic et une journee de facture Vercel. Ce document et trois items de backlog sont la sortie.

Perimetre : le ledger de credits, les crons daily-bonus et reset-credits, les plafonds qui depensent de l'argent provider (scan public, discovery, spy-full), le rate limit MCP Free, le pool Postgres.

Etat du depot : origin/main a 463d5171e, 23 septembre 2026. Trafic : Vercel Web Analytics, projet boostecom.app, production, 24 aout au 23 septembre 2026 (le 23 est une journee partielle, releve vers 02:30 UTC). Facture : team BoostEcom, fenetre 31 aout 07:00 au 1er septembre 07:00 UTC.

Verdict

Le site et environ 1 000 organisations payantes tiennent. Quelques milliers de scans anonymes, non. Le premier job billing qui rate du travail est daily-bonus, vers 1 500 a 3 000 orgs payantes actives le meme jour, et il rate en sautant la queue.

La prod est deux ordres de grandeur en dessous : 4 329 pages vues sur 31 jours, 18 visiteurs au pic d'une journee.

Ce qui a ete verifie

SondeResultat
Double depense d'un meme soldefermee : pg_advisory_xact_lock + ligne hold (credits-check.ts)
Double grant mensuel / bonusferme : MonthlyReset(orgId, year, month), DailyBonus(orgId, day)
Overdraft au-dela du soldeferme : refus si l'estime depasse le solde, abort mid-stream
Free qui brule le Gateway via le chatferme : FREE_TIER refuse avant le solde
daily-bonus a 300 ssequentiel, pas de curseur repris au timeout
reset-credits a 300 smeme forme, mais seulement les ancres dues ce jour (~1/30)
Scan public anonyme500/jour, 50 en vol, 600 minutes Browserbase / mois
Discovery payant / spy-full100 / mois et 120 / mois, globaux
MCP Free50/min et 2 000/jour par org, rien sur la somme
Pool pgmax laisse au defaut (10 / instance), attachDatabasePool non cable (data-platform/0528)
Webhooks Stripeclaim idempotent StripeEvent. Le volume de quelques milliers d'abos n'est pas le sujet

Constat 1 — les visiteurs qui lisent ne coutent pas le ledger

Chaque page est rendue a la requete (nonce CSP, ISR inerte). 5 000 visiteurs par jour, c'est une facture Fluid, pas une facture Stripe.

La journee de facture observee est a $6.47, dont $4.79 de Build CPU. Ce n'est pas un run-rate : un build n'a rien a voir avec les visiteurs, et les 67 crons sont deja dans les lignes Fluid ($0.78 de memoire provisionnee, $0.22 de CPU actif, $0.01 d'invocations). On ne multiplie pas cette journee par 30, ni par un ratio de visiteurs.

Constat 2 — les surfaces qui depensent s'arretent a quelques centaines de scans

SurfacePlafondFichier
Scan public Browserbase500/jour, 50 concurrents, 600 min/moissrc/features/tracking/lib/public-scan-budget.ts
Sondes payantes d'onboarding100/mois, globalsrc/services/billing/discovery-scan-budget.ts
Spy-full120/mois, globalsrc/services/algorithms/intelligence/spy-full-budget.ts

600 minutes a 110 s de plafond par scan, c'est de l'ordre de 300 scans complets par mois si chacun va au bout. Les defauts sont le produit. Les relever est une depense, pas un correctif. Item billing/2964.

Constat 3 — daily-bonus est le mur des orgs payantes

src/app/api/cron/daily-bonus/route.ts : maxDuration = 300, chunks de 500, puis pour chaque org eligible un await de transaction, un await d'audit, un await d'invalidation de cache. Pas de curseur persiste. Au timeout, les ids qui trient en dernier ne recoivent pas le bonus du jour. @@unique([orgId, day]) empeche le double paiement au retry, il ne rattrape pas la queue. Vercel ne rejoue pas le cron.

La latence par grant n'a pas ete tracee. A 80-400 ms par org (trois allers-retours), 300 s couvre 750 a 3 750 grants. A 200 ms et 50 % d'orgs actives (hypothese login Pro du financial-model.md), le mur est vers 3 000 orgs payantes. Le jour ou tout le monde a ete actif dans les 24 h, vers 1 500. Item platform-ops/2960.

reset-credits a la meme forme mais ne voit que l'anniversaire. Il tient plus loin, y compris le jour ou les ancres 29-31 se rabattent.

Constat 4 — la marge tient, meme si tout le monde brule tout

Prix et grants lus dans PLAN_PRICING (src/types/billing-plans.ts). Markup 1.5×. Stripe 2.9 % + $0.30. Infra tenue au $0.50/org du modele, qui n'est pas une mesure.

PlanPrixMarge modeleMarge si 100 % du grant et 100 % du bonus
Pro$7969 % ($54.58)29 % ($23.24)
Max 5x$19971 % ($140.70)17 % ($33.10)
Max 20x$39980 % ($319.30)22 % ($87.30)

Mix de planification, pas le ledger reel : 800 Pro, 150 Max 5x, 50 Max 20x. MRR $113 000. Contribution ~$81k au taux du modele, ~$28k si tout est brule. Les credits achetes restent ~30 % au pire cas (tableau derive de financial-model.md).

Le cap de concurrence IA (1 / 2 / 5 / 10) fail open si Redis ne repond pas. Le quota AI Gateway n'est pas dans le depot.

Constat 5 — le Free ne brule pas le chat, il peut bruler la somme des plafonds MCP

PLAN_MCP_RATE_LIMITS : Free 50/min, 2 000/jour, par org. Mille orgs Free qui saturent le plafond, c'est 2 millions d'appels MCP par jour, chacun une fonction et une lecture Store. Item integrations/2962.

Ce que cet audit n'a pas mesure

  • connexions Neon en prod
  • depense AI Gateway
  • nombre de clients Stripe
  • latence reelle d'un grant daily-bonus