Repository navigation
fix(cli): billing re-reads the account on Enter and sells nothing it can't price - #7226
Conversation
…can't price Two holes in `billing subscribe` / `billing add-credits`: - After paying on the pricing page, "Press Enter to pay" was still waiting. Pressing it before the next 2-second poll opened a second Stripe checkout. Enter now reads the plan again: nothing opens when the page has already covered it, and a partial top-up on the page is priced from the new balance. - One failed first plan read made the account look like Free with 0 credits: it offered Pro to a Pro account and priced add-on credits for the whole run, not the shortfall. The first read is retried; if the plan still can't be read, no terminal checkout opens and the page finishes it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HebQXN7wsMK6GqT3MNtqXw
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at 845a584d46811bd1cc358133b69f07fee414b42d.
The initial null-plan retry/refusal and Enter-time successful-read coverage/shortfall checks address the reported cases (packages/cli/src/commands/billing.ts:77–85,103–120,200–210). addonBlocks remains the shared quote/checkout rounding function: a verified Pro balance moving from 1000 to 4000 with --needed 5000 requests one 1000-credit pack, and a balance of 5000 creates none. Offer min/max clamping is unchanged, so this does not promise that a single capped checkout can cover every arbitrarily large need.
Pre-existing follow-up — fail-closed is still partial. This fixes the initial null read case, including unattended add-credits --needed, but not every unreadable-plan path. Enter's null read falls back to the original shortfall (:103–106). Also readPlanBefore accepts a truthy mock:true fallback (:116–120; real plan.ts:57–74 returns it after HTTP failure). Actual command/plan probes show initial 1000 → Enter 503 still creates four packs, and all subscription reads 503 with HYPERFRAMES_MOCK_PLAN=pro:1000 prints that checkout unattended. These stale/mock purchases were already possible on main; this patch does not worsen them. Follow up by refusing null/mock snapshots at both checkout gates while leaving the page/poll active.
Nit/follow-up — quote refresh and reconfirmation remain absent. billing.ts:83–85 displays a changed pack total and creates its session immediately after the first Enter; it also reuses the initial offer. A balance falling 4000 → 1000 changes a $1 quote into a $4 checkout for the now-correct shortfall. Stripe still presents its price before payment; this is not an automatic or unconfirmed charge. To restore the safeguards described in closed #7085, refresh the offer too and ask again whenever the displayed quote changes, proceeding only when a newly verified quote matches the quote accepted by Enter. Missing Pro-price refusal remains absent; absent add-on offers already refuse in createCheckout.
Validation: full root bun run build passes. bun run --cwd packages/cli test src/commands/billing.test.ts src/heygen/plan.test.ts passes 36/36 at this head. Swapping in current main (216395e98e094972ec905e20c851149ab302a6f1) billing source makes exactly the four new tests fail (32 pass); selecting only their describe block gives 4 failures. Failures are duplicate checkout, 4000 versus 1000 shortfall, checkout after unreadable plan, and 5000 versus 1000 after one failed read. Restored source passes 36/36; worktree clean. plan.ts and its tests are identical on main/head. Initial borrowed-dependency build/collection failures were resolved by a frozen install before these results; local Bun is 1.3.14 versus repository engine 1.4.4. Independent actual-command/real-plan probes use fake fetch and terminal/browser/auth seams; root reproduced Enter-null, initial-mock and balance-drop. No live purchase, HeyGen/Stripe request, real browser or terminal used. Existing stop-signal/late-work cancellation is retained; webhook settlement lag is outside a client re-read's guarantee.
Need: yes · Size: ok · Reuse: addonBlocks and existing cancellation signal · Simpler: bounded re-read/retry added to the existing flow · Structure: fine
Risk: medium — billing creates checkout sessions from asynchronous account balances; inherited verification/quote follow-ups remain.
Verdict: APPROVE
Reasoning: The patch fixes the four reported cases without a verified new blocking regression. This approves the increment, not a claim that all #7085 verified-plan and quote-consent safeguards have landed.
— Rames (hf-review)
* chore: release v0.9.16 * chore: format v0.9.16 release notes * chore: add heygen-com#7228 and heygen-com#7231 to v0.9.16 notes * chore: add heygen-com#7217 heygen-com#7224 heygen-com#7225 heygen-com#7226 heygen-com#7229 heygen-com#7233 to v0.9.16 notes
What was wrong
I ran the real
billingcommand andplan.tsfrom main against a scripted fake billing server. Two cases sold the wrong thing.add-credits --needed 5000on an account holding 4000 priced all 5000 credits, not the 1000 still short. Without a terminal, it printed that checkout link.The fix
Simulation (fake server, fake clock)
subscribeThese cases are unchanged:
subscribestill opens Stripe, and Stripe's own page shows the price.Tests
billing.test.tsfail on main and pass here:billing.test.tsandplan.test.ts36 passed. Typecheck, oxlint and oxfmt are clean. The comment share inbilling.tswent down.🤖 Generated with Claude Code
https://claude.ai/code/session_01HebQXN7wsMK6GqT3MNtqXw