Skip to content

fix(credits): seed new accounts at the right plan-aware balance#549

Merged
sweetmantech merged 1 commit into
testfrom
feat/plan-aware-credit-seeding
May 11, 2026
Merged

fix(credits): seed new accounts at the right plan-aware balance#549
sweetmantech merged 1 commit into
testfrom
feat/plan-aware-credit-seeding

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented May 11, 2026

Summary

Follow-up to #547. Brand-new accounts were being seeded with exactly 25 credits regardless of plan, because insertCreditsUsage had a hardcoded fallback default of 25. The new credit-balance endpoint surfaced this as "25 / 333 used 308" the moment an agent signup or account create call completes (see the screenshot in #547 — free-tier test account).

This PR fixes it at the api layer, reusing the constants and Stripe-lookup logic shipped in #547 instead of duplicating them.

What changed

1. Extracted the shared plan-detection. PR #547's checkAndResetCredits had getActiveSubscriptionDetails + getOrgSubscription + isActiveSubscription glue inline. Pulled it into a new lib/credits/getAccountSubscriptionState.ts so write paths and read paths share one truth.

```ts
export async function getAccountSubscriptionState(accountId: string): Promise<{
isPro: boolean;
activeSubscription: Stripe.Subscription | null;
}>
```

2. Refactored `checkAndResetCredits` to use it. Behavior unchanged — 7 lines of inline subscription handling collapse to 2.

3. Added `lib/credits/initializeAccountCredits.ts` — plan-aware seeder. Uses the same `DEFAULT_CREDITS = 333` / `PRO_CREDITS = 1000` constants from #547 (`lib/credits/const.ts`), looks up subscription state via the new helper, then calls the lower-level insert.

```ts
export async function initializeAccountCredits(accountId: string) {
const { isPro } = await getAccountSubscriptionState(accountId);
return insertCreditsUsage(accountId, isPro ? PRO_CREDITS : DEFAULT_CREDITS);
}
```

4. Swapped call sites to use the seeder instead of the raw DB op:

  • `lib/agents/createAccountWithEmail.ts:30` (`POST /api/agents/signup` path)
  • `lib/accounts/createAccountHandler.ts:94` (`POST /api/accounts` path)

5. Removed the booby-trap default from `insertCreditsUsage.ts`. The `remainingCredits` parameter is now required — any new caller that forgets to choose a plan-aware value gets a TypeScript error instead of silently inheriting 25.

DRY proof

After this PR:

Concern Source of truth
Credit limits `lib/credits/const.ts` (from #547)
"Is this account pro?" `lib/credits/getAccountSubscriptionState.ts` (new, consumed by both read and write paths)
Refill semantics `lib/credits/checkAndResetCredits.ts` (from #547, now thinner)
Seeding `lib/credits/initializeAccountCredits.ts` (new)
DB insert `lib/supabase/credits_usage/insertCreditsUsage.ts` (still in `lib/supabase/` per `CLAUDE.md` — pure DB op, no defaults)

TDD evidence

  • `getAccountSubscriptionState.test.ts` — 4 tests (free, account-active, org-active, canceled-trial → free) — RED → GREEN
  • `initializeAccountCredits.test.ts` — 3 tests (free seeds 333, pro seeds 1000, insert-null propagates) — RED → GREEN
  • `checkAndResetCredits.test.ts` — migrated to mock the new helper instead of 2 Stripe functions; behavior unchanged
  • `createAccountWithEmail.test.ts`, `agentSignupHandler.test.ts`, `createAccountHandler.test.ts` — mocks updated to point at the new seeder

234 tests green across 39 files, lint clean, no typecheck regressions in changed files.

Out of scope

  • `chat/lib/supabase/credits_usage/initializeAccountCredits.ts` is now structurally redundant with this new api version. Worth a separate chat-side PR once we want chat to consume the public credits surface instead.

Test plan

  • CI passes
  • Curl `POST /api/agents/signup` against preview and observe the new account starts at 333 / 333 (not 25 / 333)
  • Pre-existing pro account (`sweetmantech@gmail.com`) still reads 430 / 1,000 — no read-path regression

🤖 Generated with Claude Code


Summary by cubic

New accounts now start with the correct plan-aware credits (333 free, 1000 pro) instead of a hardcoded 25. Centralized subscription detection and removed the fallback to fix the wrong initial balance shown by the credits endpoint.

  • Bug Fixes

    • Added lib/credits/initializeAccountCredits and use it in lib/agents/createAccountWithEmail and lib/accounts/createAccountHandler to seed plan-aware credits.
    • Made insertCreditsUsage(accountId, remainingCredits) require an explicit balance; removed the 25-credit default.
  • Refactors

    • Introduced lib/credits/getAccountSubscriptionState as the single source of truth for plan status; checkAndResetCredits now uses it.

Written for commit da69293. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Account credits are now automatically initialized based on subscription tier during account creation, with Pro accounts receiving higher credit amounts than free accounts.
  • Refactor

    • Streamlined credit initialization and subscription state handling to improve consistency and reduce code duplication across all account creation flows.

Review Change Stack

Brand-new accounts used to receive exactly 25 credits regardless of
plan, because insertCreditsUsage had a hard-coded DEFAULT_CREDITS=25
fallback that both call sites (agent signup + account create) relied
on. The new credits-balance endpoint exposes this as "25 / 333 used 308"
the moment an account is provisioned.

Fix at the API layer, reusing what PR #547 already gave us:

- New `lib/credits/getAccountSubscriptionState.ts` — single source of
  truth for "is this account pro?". Extracts the parallel
  getActiveSubscriptionDetails + getOrgSubscription lookup that
  checkAndResetCredits already did inline.

- `checkAndResetCredits` now delegates to that helper. Behavior
  unchanged; 7 lines collapse to 2.

- New `lib/credits/initializeAccountCredits.ts` — plan-aware seeder.
  Looks up the subscription state via the new helper, then calls
  insertCreditsUsage with PRO_CREDITS=1000 or DEFAULT_CREDITS=333
  (the constants we already exported in PR #547).

- Both call sites swap insertCreditsUsage(id) for
  initializeAccountCredits(id):
  - lib/agents/createAccountWithEmail.ts
  - lib/accounts/createAccountHandler.ts

- Remove the booby-trap default from insertCreditsUsage. The
  remainingCredits parameter is now required, so any new caller that
  forgets to pick a plan-aware value gets a type error.

TDD: 4 new tests for getAccountSubscriptionState, 3 for
initializeAccountCredits, full checkAndResetCredits suite migrated to
mock the new helper instead of three Stripe functions. 234 tests
green across 39 files. lint clean. No typecheck regressions in
changed files (pre-existing AI-SDK type drift in getCreditUsage.test
and handleChatCredits.test is unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 11, 2026 8:43pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This PR refactors account creation and credit initialization to use a centralized subscription-state helper. It introduces getAccountSubscriptionState to consolidate Pro/Default determination, adds initializeAccountCredits to seed credits based on subscription tier, removes unsafe defaults from insertCreditsUsage, and updates account-creation and credit-reset endpoints to use the new abstractions.

Changes

Credits Initialization Refactor

Layer / File(s) Summary
Subscription State Helper
lib/credits/getAccountSubscriptionState.ts
New module exports AccountSubscriptionState interface and getAccountSubscriptionState(accountId) function. Fetches account and org Stripe subscriptions in parallel, determines whether each is active, and returns { isPro, activeSubscription } with account-subscription priority when both are active.
Credit Initialization Helper
lib/credits/initializeAccountCredits.ts
New module exports initializeAccountCredits(accountId). Calls getAccountSubscriptionState to determine isPro, selects PRO_CREDITS or DEFAULT_CREDITS, and delegates persistence to insertCreditsUsage.
Credit Insertion Signature Update
lib/supabase/credits_usage/insertCreditsUsage.ts
Removes default parameter remainingCredits: number = DEFAULT_CREDITS and the exported DEFAULT_CREDITS constant. Function now requires explicit remainingCredits argument; JSDoc directs callers to use initializeAccountCredits for plan-aware defaults.
Credit Reset Logic Update
lib/credits/checkAndResetCredits.ts
Replaces direct Stripe subscription fetching with call to getAccountSubscriptionState(accountId), destructuring isPro and activeSubscription. Updates subscription start-time derivation to use activeSubscription?.current_period_start or activeSubscription?.start_date. Refill eligibility logic remains unchanged.
Account Creation Endpoints
lib/accounts/createAccountHandler.ts, lib/agents/createAccountWithEmail.ts
Both handlers switch from insertCreditsUsage(accountId) to initializeAccountCredits(accountId). Error messages updated to reflect new function name. New accounts now receive credits based on subscription status at creation time.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • recoupable/api#520: Both PRs introduce and refine the subscription-state utilities (isActiveSubscription, account/org subscription fetching) used by the new getAccountSubscriptionState helper.
  • recoupable/api#544: The refactored account-creation flow in this PR directly affects the same account-provisioning path used by that PR's authentication and account-lookup handlers.
  • recoupable/api#547: Both PRs modify the credits subsystem, specifically updating checkAndResetCredits behavior and changing credit-row initialization patterns via insertCreditsUsage and the new initializeAccountCredits.

Poem

✨ Credits flow from subscriptions true,
One helper now does what many used to do—
Pro or default, the state declares,
Account creation no longer bears the weight alone—
Refactored, reused, and crystal-clear. 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Inconsistent error handling: createAccountWithEmail validates initializeAccountCredits but createAccountHandler does not, risking accounts without credit rows. Add null check to createAccountHandler line 94 matching createAccountWithEmail's pattern to ensure credits initialization doesn't silently fail.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plan-aware-credit-seeding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
lib/credits/getAccountSubscriptionState.ts (1)

31-31: 💤 Low value

Consider extracting the subscription selection logic for clarity.

The nested ternary on line 31 is correct but could be more readable. In "Chill" mode this is fine, but if you find yourself revisiting this code, consider a small helper or early-return pattern.

♻️ Optional: explicit conditional for clarity
- activeSubscription: hasAccountSub ? accountSub : hasOrgSub ? orgSub : null,
+ activeSubscription: hasAccountSub ? accountSub : (hasOrgSub ? orgSub : null),

Or extract to a helper variable before the return:

  const hasAccountSub = isActiveSubscription(accountSub);
  const hasOrgSub = isActiveSubscription(orgSub);
+ const activeSubscription = hasAccountSub ? accountSub : hasOrgSub ? orgSub : null;
  return {
    isPro: hasAccountSub || hasOrgSub,
-   activeSubscription: hasAccountSub ? accountSub : hasOrgSub ? orgSub : null,
+   activeSubscription,
  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/credits/getAccountSubscriptionState.ts` at line 31, The nested ternary
setting activeSubscription is hard to read; in getAccountSubscriptionState
extract that logic into a small helper or local variable (e.g.,
computeActiveSubscription or activeSubscription variable) that takes
hasAccountSub, accountSub, hasOrgSub, orgSub and returns accountSub if
hasAccountSub, otherwise orgSub if hasOrgSub, otherwise null, then use that
variable in the returned object to replace the inline ternary.
lib/accounts/createAccountHandler.ts (1)

33-111: ⚖️ Poor tradeoff

Consider breaking down this 79-line function.

The function exceeds the 50-line guideline for lib/**/*.ts and handles multiple responsibilities: email lookup, wallet lookup, and new-account creation. Each path could be extracted into a focused helper (e.g., findAccountByEmail, findAccountByWallet, createNewAccount).

Given "Chill" mode and that the PR doesn't touch the overall structure, this is deferrable—but worth noting for future refactoring.

As per coding guidelines: "Keep functions under 50 lines" and "Keep functions small and focused."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/accounts/createAccountHandler.ts` around lines 33 - 111,
createAccountHandler is too long and mixes responsibilities (email lookup,
wallet lookup, creation); split it into focused helpers. Extract the email flow
(use selectAccountByEmail, assignAccountToOrg, getAccountWithDetails) into
findAccountByEmail, the wallet flow (selectAccountByWallet and the
accountInfo/account_emails/account_wallets flattening) into findAccountByWallet,
and the creation flow (insertAccount, insertAccountEmail, insertAccountWallet,
initializeAccountCredits, assignAccountToOrg) into createNewAccount; then have
createAccountHandler orchestrate these helpers and keep it under ~50 lines.
Ensure the existing error handling and NextResponse usage remain the same and
reuse the same types (CreateAccountBody, AccountDataResponse).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/accounts/createAccountHandler.ts`:
- Line 94: The call to initializeAccountCredits in createAccountHandler
currently ignores failures and can leave an account without a credits_usage row;
change the call to capture the return value (e.g., const credits = await
initializeAccountCredits(newAccount.id)) and if it is null/falsey throw an error
(or otherwise abort the handler) similar to createAccountWithEmail’s behavior so
the account creation does not silently proceed without initialized credits;
reference initializeAccountCredits and createAccountHandler (and mirror the
error handling used in createAccountWithEmail) to locate and implement the fix.

---

Nitpick comments:
In `@lib/accounts/createAccountHandler.ts`:
- Around line 33-111: createAccountHandler is too long and mixes
responsibilities (email lookup, wallet lookup, creation); split it into focused
helpers. Extract the email flow (use selectAccountByEmail, assignAccountToOrg,
getAccountWithDetails) into findAccountByEmail, the wallet flow
(selectAccountByWallet and the accountInfo/account_emails/account_wallets
flattening) into findAccountByWallet, and the creation flow (insertAccount,
insertAccountEmail, insertAccountWallet, initializeAccountCredits,
assignAccountToOrg) into createNewAccount; then have createAccountHandler
orchestrate these helpers and keep it under ~50 lines. Ensure the existing error
handling and NextResponse usage remain the same and reuse the same types
(CreateAccountBody, AccountDataResponse).

In `@lib/credits/getAccountSubscriptionState.ts`:
- Line 31: The nested ternary setting activeSubscription is hard to read; in
getAccountSubscriptionState extract that logic into a small helper or local
variable (e.g., computeActiveSubscription or activeSubscription variable) that
takes hasAccountSub, accountSub, hasOrgSub, orgSub and returns accountSub if
hasAccountSub, otherwise orgSub if hasOrgSub, otherwise null, then use that
variable in the returned object to replace the inline ternary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6616afb3-5b6a-4993-8871-4ff6a4e844fe

📥 Commits

Reviewing files that changed from the base of the PR and between 6b885c2 and da69293.

⛔ Files ignored due to path filters (6)
  • lib/accounts/__tests__/createAccountHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agents/__tests__/agentSignupHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/agents/__tests__/createAccountWithEmail.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/checkAndResetCredits.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/getAccountSubscriptionState.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/credits/__tests__/initializeAccountCredits.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (6)
  • lib/accounts/createAccountHandler.ts
  • lib/agents/createAccountWithEmail.ts
  • lib/credits/checkAndResetCredits.ts
  • lib/credits/getAccountSubscriptionState.ts
  • lib/credits/initializeAccountCredits.ts
  • lib/supabase/credits_usage/insertCreditsUsage.ts

}

await insertCreditsUsage(newAccount.id);
await initializeAccountCredits(newAccount.id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for credit initialization.

Unlike createAccountWithEmail (which throws if initializeAccountCredits returns null), this handler silently proceeds when credit initialization fails. If initializeAccountCredits returns null, the account will exist without a credits_usage row, causing downstream read failures.

🛡️ Recommended fix
- await initializeAccountCredits(newAccount.id);
+ const credits = await initializeAccountCredits(newAccount.id);
+ if (!credits) {
+   throw new Error("Failed to initialize account credits");
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await initializeAccountCredits(newAccount.id);
const credits = await initializeAccountCredits(newAccount.id);
if (!credits) {
throw new Error("Failed to initialize account credits");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/accounts/createAccountHandler.ts` at line 94, The call to
initializeAccountCredits in createAccountHandler currently ignores failures and
can leave an account without a credits_usage row; change the call to capture the
return value (e.g., const credits = await
initializeAccountCredits(newAccount.id)) and if it is null/falsey throw an error
(or otherwise abort the handler) similar to createAccountWithEmail’s behavior so
the account creation does not silently proceed without initialized credits;
reference initializeAccountCredits and createAccountHandler (and mirror the
error handling used in createAccountWithEmail) to locate and implement the fix.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 12 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Client as Client (HTTP)
    participant Handler as Account Creation Handler
    participant InitCredits as initializeAccountCredits
    participant SubState as getAccountSubscriptionState
    participant Stripe as Stripe API
    participant Insert as insertCreditsUsage (DB)

    Note over Client,Insert: Account creation flow with plan-aware credit seeding

    Client->>Handler: POST /api/agents/signup or POST /api/accounts
    Handler->>Handler: Create account record

    Handler->>InitCredits: initializeAccountCredits(accountId)

    InitCredits->>SubState: getAccountSubscriptionState(accountId)

    SubState->>Stripe: getActiveSubscriptionDetails(accountId)
    SubState->>Stripe: getOrgSubscription(accountId)
    Stripe-->>SubState: subscription details (or null)

    SubState->>SubState: isActiveSubscription check on each
    alt Account has active subscription
        SubState-->>InitCredits: { isPro: true, activeSubscription: accountSub }
    else No account sub but org has active sub
        SubState-->>InitCredits: { isPro: true, activeSubscription: orgSub }
    else Neither has active subscription
        SubState-->>InitCredits: { isPro: false, activeSubscription: null }
    end

    alt isPro is true
        InitCredits->>Insert: insertCreditsUsage(accountId, PRO_CREDITS=1000)
    else isPro is false
        InitCredits->>Insert: insertCreditsUsage(accountId, DEFAULT_CREDITS=333)
    end

    Insert->>Insert: INSERT into credits_usage
    Insert-->>InitCredits: credits_usage record (or null)

    alt Insert succeeds
        InitCredits-->>Handler: credits_usage row
    else Insert returns null
        InitCredits-->>Handler: null
        Handler->>Handler: Throw error (account creation aborted)
    end

    Handler-->>Client: New account response with correct credit balance
Loading

Requires human review: This PR refactors credit initialization by extracting subscription state logic, modifying account creation paths, and removing a default parameter from a database insert—changes that touch core business logic and carry moderate risk if a bug is introduced, so human review is warranted despite solid

@sweetmantech
Copy link
Copy Markdown
Contributor Author

Manual verification on the preview deployment

Preview URL: https://api-git-feat-plan-aware-credit-seeding-recoup.vercel.app

1. The fix — fresh agent now seeds at 333

PREVIEW="https://api-git-feat-plan-aware-credit-seeding-recoup.vercel.app"
TS=$(date +%s)-$RANDOM
RESP=$(curl -s -X POST "$PREVIEW/api/agents/signup" \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"agent+plan-aware-${TS}@recoupable.com\"}")
# → { account_id: "5fdaccfa-...", api_key: "recoup_sk_..." }
KEY=$(echo "$RESP" | jq -r .api_key)
ACCOUNT=$(echo "$RESP" | jq -r .account_id)

curl -s -H "x-api-key: $KEY" "$PREVIEW/api/accounts/$ACCOUNT/credits"
{
  "account_id": "5fdaccfa-7cd5-4d72-8c9c-443ad81fe604",
  "remaining_credits": 333,
  "total_credits": 333,
  "used_credits": 0,
  "is_pro": false,
  "timestamp": "2026-05-11T21:06:23.51295"
}

333 / 333 / used 0 — exactly what we want for a free-tier account on day one. Compare to the same POST /api/agents/signup flow on the previous preview (pre-fix), where the response was 25 / 333 / used 308.

2. No read-path regression — pro account is unchanged

curl -s -H "x-api-key: $MAINTAINER_KEY" \
  "$PREVIEW/api/accounts/fb678396-a68f-4294-ae50-b8cacf9ce77b/credits"
{
  "account_id": "fb678396-a68f-4294-ae50-b8cacf9ce77b",
  "remaining_credits": 430,
  "total_credits": 1000,
  "used_credits": 570,
  "is_pro": true,
  "timestamp": "2026-04-24T17:50:43.475"
}

✅ Identical to the same query on the #547 preview. The checkAndResetCredits refactor (now delegating to getAccountSubscriptionState) is behavior-preserving.

Summary

Account Before #549 After #549 Status
Fresh free-tier agent (just provisioned via POST /api/agents/signup) 25 / 333 used 308 333 / 333 used 0 ✅ Fixed
Maintainer pro account 430 / 1,000 used 570 430 / 1,000 used 570 ✅ Unchanged

Both the write-path fix (initializeAccountCredits seeds the right number) and the read-path refactor (checkAndResetCreditsgetAccountSubscriptionState) work as designed. No pro-tier signup case tested directly on preview — we'd need to mint a brand-new account that already has an active Stripe subscription, which isn't constructible from the CLI. Unit-tested instead (initializeAccountCredits.test.ts: "seeds PRO_CREDITS when the account already has an active subscription").

🤖 Generated with Claude Code

@sweetmantech sweetmantech merged commit 99d9cdd into test May 11, 2026
6 checks passed
@sweetmantech sweetmantech deleted the feat/plan-aware-credit-seeding branch May 11, 2026 21:16
sweetmantech added a commit that referenced this pull request May 11, 2026
#550)

Brand-new accounts used to receive exactly 25 credits regardless of
plan, because insertCreditsUsage had a hard-coded DEFAULT_CREDITS=25
fallback that both call sites (agent signup + account create) relied
on. The new credits-balance endpoint exposes this as "25 / 333 used 308"
the moment an account is provisioned.

Fix at the API layer, reusing what PR #547 already gave us:

- New `lib/credits/getAccountSubscriptionState.ts` — single source of
  truth for "is this account pro?". Extracts the parallel
  getActiveSubscriptionDetails + getOrgSubscription lookup that
  checkAndResetCredits already did inline.

- `checkAndResetCredits` now delegates to that helper. Behavior
  unchanged; 7 lines collapse to 2.

- New `lib/credits/initializeAccountCredits.ts` — plan-aware seeder.
  Looks up the subscription state via the new helper, then calls
  insertCreditsUsage with PRO_CREDITS=1000 or DEFAULT_CREDITS=333
  (the constants we already exported in PR #547).

- Both call sites swap insertCreditsUsage(id) for
  initializeAccountCredits(id):
  - lib/agents/createAccountWithEmail.ts
  - lib/accounts/createAccountHandler.ts

- Remove the booby-trap default from insertCreditsUsage. The
  remainingCredits parameter is now required, so any new caller that
  forgets to pick a plan-aware value gets a type error.

TDD: 4 new tests for getAccountSubscriptionState, 3 for
initializeAccountCredits, full checkAndResetCredits suite migrated to
mock the new helper instead of three Stripe functions. 234 tests
green across 39 files. lint clean. No typecheck regressions in
changed files (pre-existing AI-SDK type drift in getCreditUsage.test
and handleChatCredits.test is unchanged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant