fix(billing): distinguish team seats from AI model access - #3415
Merged
Conversation
Seats and model access both cost $10 per user and were rendered as two near-identical cards, so people bought one believing they had bought the other — and seat checkout runs in Stripe setup mode ($0 today) while model access charges the whole team immediately, which the page never said. Billing now opens on a single statement that names both line items with their own status and price and sums them into a total, so no charge is unaccounted for. Each card states who is billed, and every action is paired with a sentence describing when to use it instead of standing alone as a bare button. Deployments without Stripe keys no longer quote prices or offer a checkout the operator cannot complete. Adds DenUsageMeter, DenActionRow/DenActionList, and DenLineItemRow/DenMarkTile, and extends DenNotice with neutral and warning tones. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Member
Author
benjaminshafii
enabled auto-merge (squash)
August 1, 2026 16:42
There was a problem hiding this comment.
Warden security clearance: clear. No new security issues found in this diff (354ba2f4d90e78375e81713899f7ee59bbbb795e). Automated clearance satisfies the required-review gate only — a human still reviews and merges. Analysis run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
Seats and AI model access both cost $10 per user and were rendered as two near-identical cards. The two products bill completely differently:
mode: "setup". Charges $0 today; only bills for users beyond the free 5.mode: "subscription". Bills immediately for every active member.The page never said this, so a user could buy model access believing they had bought "5 seats" and be surprised by the charge. Nothing on the page stated the whole-team total before checkout.
What changed
One statement instead of two lookalike cards. Billing now opens on
Your subscriptions, listing Team seats and AI model access as separate line items — each with its own status badge and price — summed into aTotal. The fraimz asserts the two rows arithmetically equal the total, so no charge can appear unaccounted for.Every action says when to use it. Bare buttons are replaced by action rows pairing each control with a plain-language sentence, e.g.
Add paid seatsnow reads "Only needed once you pass 5 users. Saves a card now, then charges $10.00 per extra user each month" — which is the honest description of Stripe setup mode.The whole-team price is stated before checkout. When model access is off, the CTA guidance reads "Turning this on costs $20.00 per month for your 2 members, not $10.00."
Unconfigured deployments no longer lie. Without Stripe keys the page previously still quoted $10/user and offered a seat checkout the operator could never complete. It now says seat billing is not set up and there is no member limit.
Seat usage is visual. A segmented meter replaces the flat "1 free seat left" sentence, with overflow shown proportionally once a team passes the free tier.
New primitives
Built on existing Den primitives (
DenCard,DenBadge,DenSectionHeader,DenButton); added where none existed:DenUsageMeter— segmented allowance meter for "x of y included" limitsDenActionRow/DenActionList— control paired with when-to-use guidanceDenLineItemRow/DenMarkTile— read-only statement rows (the counterpart toDenSelectableRow)DenNoticeextended withneutralandwarningtonesThe page also drops its custom
rounded-2xl+ drop-shadow sections forDenCard, which forbids shadows.Tests
pnpm typecheck(den-web) — clean, noany/casts addedpnpm fraimz --flow billing-seats-vs-ai --stack den— PASSED, 3/3 frames, driven through the real den stack (MySQL + den-api + den-web) via CDP against the seededalex@acme.testorgProof posted as a follow-up comment.
Reviewer note
The guidance copy asserts two Stripe behaviors I inferred from
stripe-billing.tsbut did not exercise against live Stripe: that cancelling model access runs to period end, and that removing a member lowers the AI bill at renewal rather than immediately. Both are worth confirming against actual proration settings before this ships to hosted.Made with Cursor