Skip to content

refactor(stripe): move upgrade-page auto-approval into API#2756

Merged
tofikwest merged 3 commits intomainfrom
fix/move-stripe-auto-approve-to-api
May 5, 2026
Merged

refactor(stripe): move upgrade-page auto-approval into API#2756
tofikwest merged 3 commits intomainfrom
fix/move-stripe-auto-approve-to-api

Conversation

@tofikwest
Copy link
Copy Markdown
Contributor

@tofikwest tofikwest commented May 5, 2026

Summary

The /upgrade/{orgId} page was instantiating its own Stripe client and writing hasAccess directly to the DB from a Next.js server component. Three problems with that:

  • Doubled secret surface areaSTRIPE_SECRET_KEY had to live on the Vercel apps/app project in addition to the API. Two copies of a live Stripe key = 2× rotation, 2× leak risk, 2× misconfig opportunities. (Result: STRIPE_SECRET_KEY is not set warnings in Vercel logs because we never set the duplicate.)
  • No audit log / no RBAC on the hasAccess flip — the upgrade page bypassed AuditLogInterceptor + PermissionGuard, leaving zero audit trail of who got auto-approved.
  • Two Stripe clients drifting — the API already had StripeService (apps/api/src/stripe/stripe.service.ts) wired up correctly; the Next.js app had its own.

This PR moves both the Stripe lookup and the hasAccess write into a new API endpoint and removes the duplicate Stripe client + env var from the Next.js app.

What changed

API:

  • New endpoint POST /v1/organization-access/auto-approve (apps/api/src/organization-access/)
    • HybridAuthGuard + PermissionGuard + @RequirePermission('organization', 'update')
    • Reuses the existing StripeService — no second client
    • Returns { hasAccess, autoApproved, reason } with reasons: already-has-access / self-hosted / trycomp-email / stripe-customer / not-eligible
    • Explicit logger.log(...) on each grant (audit trail)
  • Extended StripeService with findCustomerByDomain + isDomainActiveCustomer (logic ported verbatim from apps/app/src/lib/stripe.ts)
  • New apps/api/src/stripe/domain.utils.ts with pure extractDomain + isPublicEmailDomain helpers + tests

App:

  • apps/app/src/app/(app)/upgrade/[orgId]/page.tsx calls the new API endpoint via serverApi.post(...) instead of running Stripe + DB locally
  • Soft-fail preserved: if the API errors, the booking step still renders
  • Deleted apps/app/src/lib/stripe.ts
  • Removed STRIPE_SECRET_KEY from apps/app/src/env.mjs (declaration + runtimeEnv)

Decision matrix (preserved bit-for-bit)

Condition hasAccess granted?
Org already has access yes (no-op)
process.env.SELF_HOSTED === 'true' (or NEXT_PUBLIC_SELF_HOSTED) yes
User email at @trycomp.ai yes
User email domain == org website domain AND not a public mailbox AND domain has active Stripe customer yes
Otherwise no

Behavior change to flag for reviewers

The endpoint requires organization:update (option B in the design discussion). Today, any non-deactivated member of the org could trigger auto-approval implicitly via the page; from this PR forward only members with organization:update (owner/admin and any custom role granting that permission) can. In practice owners are who land on /upgrade, so this is unlikely to matter — but it IS a behavior change. If you'd rather preserve the looser semantics, swap the controller to HybridAuthGuard only with a manual membership check.

Follow-up (NOT in this PR)

  • Remove STRIPE_SECRET_KEY from the Vercel apps/app project after this ships. Will silence the warnings in production logs.

Test plan

  • API unit tests: 17 tests across domain.utils.spec.ts + organization-access.service.spec.ts covering all decision-matrix branches (cd apps/api && npx jest src/stripe src/organization-access)
  • Typecheck: zero new errors introduced (apps/api: 68 → 67; apps/app: 24 → 24)
  • ESLint: clean for all changed files
  • Smoke test on staging:
    • /upgrade/{orgId} as a @trycomp.ai user → auto-approved, redirected to onboarding/org
    • /upgrade/{orgId} as a non-trycomp user with no Stripe match → booking step renders
    • /upgrade/{orgId} as a user whose email domain matches the org website AND is in Stripe → auto-approved
    • Confirm Vercel STRIPE_SECRET_KEY is not set warnings stop appearing after deploy
  • Verify the API audit log captures the hasAccess flip

🤖 Generated with Claude Code


Summary by cubic

Moves Stripe-domain auto-approval off the Next.js upgrade page into the API with RBAC and a single StripeService, while keeping the self-hosted branch inline on the page to avoid OSS env mismatch. Removes the duplicate Stripe client and app-side secret; behavior stays the same.

  • Refactors

    • Added POST /v1/organization-access/auto-approve guarded by HybridAuthGuard + PermissionGuard with @RequirePermission('organization', 'update'); returns { hasAccess, autoApproved, reason } and logs grants.
    • Reused StripeService; added findCustomerByDomain, isDomainActiveCustomer, and domain helpers with tests.
    • Upgrade page now calls the API for @trycomp.ai and Stripe-customer paths; kept inline self-hosted auto-approve using NEXT_PUBLIC_SELF_HOSTED and a direct DB write to preserve OSS behavior. Removed apps/app/src/lib/stripe.ts and STRIPE_SECRET_KEY from the app env.
  • Migration

    • For non-self-hosted flows, /upgrade/{orgId} now requires organization:update to trigger auto-approval; previously any member could.
    • Remove STRIPE_SECRET_KEY from the Vercel apps/app project after deploy.

Written for commit 17a8d92. Summary will update on new commits.

The /upgrade page was instantiating its own Stripe client and writing
hasAccess directly to the DB from a Next.js server component. That meant
STRIPE_SECRET_KEY had to live on Vercel in addition to the API, the
hasAccess flip skipped the API's audit log + RBAC, and we had two
Stripe clients drifting apart over time.

Move both the Stripe lookup and the hasAccess write into a new API
endpoint:

  POST /v1/organization-access/auto-approve

guarded by HybridAuthGuard + PermissionGuard with
@RequirePermission('organization', 'update'). The endpoint reuses the
existing global StripeService — no second Stripe client. Decision
matrix preserved exactly: self-hosted, @trycomp.ai email, or
domain-matched active Stripe customer.

App side: upgrade page now calls serverApi instead of importing
@/lib/stripe and writing to db; lib/stripe.ts and the
STRIPE_SECRET_KEY env declaration are removed from the Next.js app.

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

vercel Bot commented May 5, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment May 5, 2026 8:26pm
comp-framework-editor Ready Ready Preview, Comment May 5, 2026 8:26pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped May 5, 2026 8:26pm

Request Review

Copy link
Copy Markdown
Contributor

@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 11 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

NEXT_PUBLIC_SELF_HOSTED is a Next.js build-time env that the OSS Docker
deployment sets on the app container only — there is no propagation to
the API container (the root docker-compose.yml ships only app + portal
services). Moving the entire auto-approval flow into the API would have
broken self-hosted/OSS deployments, since neither SELF_HOSTED nor
NEXT_PUBLIC_SELF_HOSTED is available there.

Restore the inline self-hosted branch on the upgrade page (preserves
original behavior bit-for-bit) and route only the Stripe-customer +
@trycomp.ai paths through the API. The single remaining DB write on the
page is gated on a build-time deploy flag, not user input — so the
"all mutations through the API" rule is preserved in spirit for every
user-facing decision.

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

@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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/app/src/app/(app)/upgrade/[orgId]/page.tsx">

<violation number="1" location="apps/app/src/app/(app)/upgrade/[orgId]/page.tsx:80">
P2: Handle self-hosted auto-approve DB write failures so transient errors don’t break page rendering.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread apps/app/src/app/(app)/upgrade/[orgId]/page.tsx
@vercel vercel Bot temporarily deployed to Preview – portal May 5, 2026 20:21 Inactive
@tofikwest tofikwest merged commit 8247ed3 into main May 5, 2026
11 checks passed
@tofikwest tofikwest deleted the fix/move-stripe-auto-approve-to-api branch May 5, 2026 21:13
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