ADRADR-0010 · Un seul markup, porte par le plan

ADR-0010 — Un seul markup, porte par le plan : l'origine d'un credit ne change pas son taux

Le catalogue v3.0 declare le markup applique au cout brut de l'AI Gateway dans ADMIN_PRICING_CONFIG.markupByPlan (src/types/billing-plans.ts) : 1.5x sur Pro / Max 5x / Max 20x, 1.0x sur Custom, 2.0x en repli quand aucun…

Statut

Accepté · 2026-09-06

Piliers : billing

Contexte

Le catalogue v3.0 declare le markup applique au cout brut de l'AI Gateway dans ADMIN_PRICING_CONFIG.markupByPlan (src/types/billing-plans.ts) : 1.5x sur Pro / Max 5x / Max 20x, 1.0x sur Custom, 2.0x en repli quand aucun plan n'est resolu.

A cote vivait une SECONDE declaration, CREDIT_PURCHASE_MARKUP = 2.0, censee s'appliquer aux credits achetes sur le slider $10-$5000. Elle etait accompagnee d'un commentaire nommant un « codepath separe », calculateUserPriceForPurchase. Etat verifie le 2026-09-06, avant toute modification :

  • git grep CREDIT_PURCHASE_MARKUP rendait sa declaration et trois commentaires. Zero lecteur executable.
  • calculateUserPriceForPurchase n'existait nulle part dans le depot.
  • La seule fonction qui facture un tour, calculateUserPrice(..., { plan }), resout resolveMarkup(plan) et ecrit -userPrice au ledger (src/features/ai/orchestrator/runtime/billing.ts). Elle ne recoit aucune information sur l'origine des credits.

Trois surfaces publiaient donc un taux que rien ne pouvait facturer : billing-credits.ts, docs/business-model/credits.md, et surtout docs/business-model/financial-model.md, qui calculait sur ce 2.0x une marge d'achat de ~46-47% et la reportait dans le mix de revenu et la projection $130k MRR.

Une contrainte structurelle ferme la porte a l'autre bout : le solde est un scalaire FIFO unique (src/services/billing/credit-balance.ts, balance = max(0, N - max(0, D - E))). Un tour de chat ne consomme pas un lot identifiable, il decremente une somme. Facturer selon l'origine du credit demanderait une comptabilite par lot qui n'existe pas, et un tour qui traverse deux origines devrait etre facture a deux taux.

Décision

Le markup est une propriete du plan, jamais de l'origine du credit. ADMIN_PRICING_CONFIG.markupByPlan est le seul endroit ou un taux est ecrit. CREDIT_PURCHASE_MARKUP est supprime, et les documents qui en derivaient une marge sont recalcules sur 1.5x.

Consequence pour le client : aucune. C'est ce qui etait deja facture. Cette ADR aligne les chiffres publies sur le code, elle ne change pas un seul debit.

Alternatives écartées

OptionPourquoi non
Appliquer reellement 2x aux credits achetes (option A de billing/0362)C'est une hausse de prix : le meme dollar financerait moitie moins d'inference. Une hausse de prix se decide et s'annonce, elle ne se livre pas dans une PR de nettoyage. Techniquement, elle exige en plus une comptabilite par lot que le solde FIFO scalaire ne porte pas. Reste ouvert comme decision commerciale : billing/0411.
Vendre le pack a un ratio < 1:1, par exemple 0.75:1 (option C)Lisible sur le slider et sans changement du moteur de consommation, mais c'est la meme hausse de prix vue de l'autre cote, donc la meme decision commerciale. Egalement billing/0411.
Garder la constante « pour plus tard », avec un commentaire disant qu'elle ne sert pasC'est exactement l'etat qui a produit le defaut : trois fichiers ont recopie le 2.0 et un modele financier a calcule dessus. Une constante non lue finit par etre lue par quelqu'un qui la croit vraie.
Deplacer le markup dans PLAN_PRICINGPLAN_PRICING est le catalogue commercial (prix, credits inclus) ; le markup est un parametre de cout d'execution, edite dans l'admin pricing. Les fusionner obligerait le client a importer le markup avec les prix.

Conséquences

  • La marge reelle sur un achat de credits est celle du markup de plan moins Stripe : ~27% sur $10, ~30% au-dela. financial-model.md annoncait ~46-47%. Les projections qui en decoulaient sont revues a la baisse dans le meme mouvement : c'est le prix de la correction, et il est comptable, pas technique.
  • Le levier « marge sur les achats » n'existe plus tant que billing/0411 n'est pas tranche. Le signal qui imposerait de le rouvrir : la part des achats de credits dans le revenu qui depasse la part des abonnements, parce qu'alors la marge consolidee suit la marge d'achat.
  • Free reste a 2.0x dans markupByPlan. Ce n'est pas un tarif : c'est le repli quand aucun plan n'est resolu, et un compte free ne consomme pas de credits metres.

Comment c'est appliqué

Trois gardes, chacune verifiee par mutation avant d'etre commitee :