Skip to content

fix(cli): billing re-reads the account on Enter and sells nothing it can't price - #7226

Merged
WaterrrForever merged 1 commit into
mainfrom
miao/billing-enter-recheck
Nov 5, 2026
Merged

WaterrrForever merged 1 commit into
mainfrom
miao/billing-enter-recheck

Conversation

@WaterrrForever

Copy link
Copy Markdown
Collaborator

What was wrong

I ran the real billing command and plan.ts from main against a scripted fake billing server. Two cases sold the wrong thing.

  1. Second checkout after paying on the page. The pricing page opens first, and "Press Enter to pay" keeps waiting. If someone paid on the page and then pressed Enter before the next 2-second poll, a second Stripe checkout opened.
  2. One failed first plan read. The account then looked like Free with 0 credits:
    • Pro was offered to an account that already had Pro.
    • add-credits --needed 5000 on an account holding 4000 priced all 5000 credits, not the 1000 still short. Without a terminal, it printed that checkout link.

The fix

  • Enter reads the plan again. If the page has already covered it, nothing opens: "The page has taken the payment, so no second checkout opens." If the page sold part of it, the checkout is priced from the new balance, and the price line is printed again when it changed.
  • The first plan read is retried twice, one second apart. If the plan still can't be read, no checkout is created or printed: "Could not read this account's plan, so no checkout opens from here. Finish on the page." The page stays open and the wait goes on as before.
  • The prompt warns against paying twice. It now reads "Press Enter to pay here (not if you already paid on the page)". The payment webhook can take a few seconds to reach the plan, and the CLI cannot see a payment in that gap, so the prompt covers it.

Simulation (fake server, fake clock)

Case main this PR
Pays on page at 5 s, Enter at 5.5 s second Pro checkout none
Plan read refused, Pro holding 4000, needs 5000 5000-credit checkout none, told to finish on the page
Plan read refused, already Pro, subscribe Pro checkout none
Same, no terminal checkout link for 5000 credits none
Pays at 5 s, webhook lands 8 s later, Enter at 9 s second checkout still a second checkout (the server doesn't know yet); the prompt warns

These cases are unchanged:

  • Enter after the poll has seen the payment opens nothing.
  • With no price list, subscribe still opens Stripe, and Stripe's own page shows the price.
  • Credits that are not on sale stop the command, as before.

Tests

  • Four new tests in billing.test.ts fail on main and pass here:
    • no second checkout when Enter follows a payment on the page;
    • a partial page top-up is priced from the new balance;
    • an unreadable plan sells nothing;
    • one failed first read is retried and priced from the real balance.
  • One existing test changed. The in-flight checkout test now keeps Enter's re-read unpaid, so it still covers a checkout created while the payment lands.
  • Results: billing.test.ts and plan.test.ts 36 passed. Typecheck, oxlint and oxfmt are clean. The comment share in billing.ts went down.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HebQXN7wsMK6GqT3MNtqXw

…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 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

@WaterrrForever
WaterrrForever added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 5f7b85c Nov 5, 2026
70 checks passed
@WaterrrForever
WaterrrForever deleted the miao/billing-enter-recheck branch November 5, 2026 12:23
@heygengenesis heygengenesis Bot mentioned this pull request Nov 5, 2026
mitanshu450 pushed a commit to mitanshu450/hyperframes-video-generation that referenced this pull request Nov 5, 2026
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants