-
Notifications
You must be signed in to change notification settings - Fork 498
show products in account settings #1071
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds Stripe-backed billing: backend routes to read billing, create setup intents, and set default payment methods; new Stripe/customer helpers and schema expansions; client/server interface methods; frontend Payments UI with Stripe Elements; E2E tests and Stripe deps added. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Client UI
participant API as Backend API
participant DB as Database (Prisma)
participant Stripe as Stripe
UI->>API: POST /payments/payment-method/{type}/{id}/setup-intent
API->>DB: verify project & tenancy, fetch stripeAccountId
API->>Stripe: create SetupIntent (off_session, card)
Stripe-->>API: client_secret, id
API-->>UI: { client_secret, stripe_account_id }
UI->>Stripe: confirm Card setup with client_secret (CardElement)
Stripe-->>UI: SetupIntent succeeded
UI->>API: POST /payments/payment-method/{type}/{id}/set-default { setup_intent_id }
API->>Stripe: retrieve SetupIntent -> payment_method
API->>Stripe: update Customer.default_payment_method
API->>DB: read/write billing/customer mapping as needed
API-->>UI: { default_payment_method: { id, brand, last4, exp_month, exp_year } }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Greptile OverviewGreptile SummaryThis PR adds product listing functionality to account settings, enabling users and teams to view their purchased products (both subscriptions and one-time purchases) with metadata like renewal dates and cancellation status. Key Changes
Confidence Score: 5/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant User as User/Browser
participant Panel as PaymentsPanel
participant App as StackClientApp
participant API as Backend API
participant DB as Database
User->>Panel: View Account Settings
Panel->>App: useProducts()
App->>API: GET /api/v1/payments/products/{customer_type}/{customer_id}
API->>DB: getOwnedProductsForCustomer()
DB-->>API: Return subscriptions + one-time purchases
API->>API: Filter server-only products (if client auth)
API->>API: Sort by createdAt & apply pagination
API-->>App: Return products list with subscription metadata
App-->>Panel: Display products with details
Panel-->>User: Show products with renewal dates & cancel buttons
alt Cancel Subscription
User->>Panel: Click "Cancel subscription"
Panel->>App: cancelSubscription({productId})
App->>API: DELETE /api/v1/payments/products/{customer_type}/{customer_id}
API->>API: Verify user owns product & permissions
API->>DB: Find active subscription
alt Has Stripe subscription
API->>Stripe: Cancel subscription
end
API->>DB: Update subscription status to canceled
DB-->>API: Success
API-->>App: {success: true}
App->>App: Invalidate products cache
App-->>Panel: Refresh products list
Panel-->>User: Show updated products (removed)
end
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
10 files reviewed, no comments
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
N2D4
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very exciting :]
packages/template/src/components-page/account-settings/payments/payments-page.tsx
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md -->
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In
@apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts:
- Around line 10-31: The function ensureClientCanAccessCustomer is duplicated;
extract it into a single shared module (e.g., export a named function
ensureClientCanAccessCustomer) and have this file and the other duplicates
import it instead of redefining it. Move/keep its dependencies and types
(KnownErrors.UserAuthenticationRequired, StatusError, getPrismaClientForTenancy,
ensureUserTeamPermissionExists) imported by the new module, preserve the same
signature { customer_type: "user" | "team", customer_id: string }, fullReq,
tenancy and behavior, export it, then replace the duplicate implementations with
an import and call to the shared ensureClientCanAccessCustomer to remove
redundancy.
In @apps/backend/src/lib/payments.tsx:
- Around line 431-433: The file is missing the CustomerType enum import used by
customerTypeToStripeCustomerType; update the existing import from
"@/generated/prisma/client" to include CustomerType so
customerTypeToStripeCustomerType can reference CustomerType.USER and
CustomerType.TEAM without errors (add CustomerType to the import list alongside
PurchaseCreationSource and SubscriptionStatus).
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Line 65: The direct access to document.documentElement.style when computing
the darkMode constant in payments-panel.tsx is SSR-unsafe; change this to
client-side detection by replacing the top-level const darkMode with state and
effect (e.g., useState for darkMode and useEffect to read
document.documentElement.style or matchMedia on mount), or use a SSR-safe hook
like a usePrefersColorScheme/useMediaQuery to set darkMode inside useEffect so
no document access happens during server render; update any uses of darkMode in
the component to read from the new state/hook.
- Around line 80-104: The Button's inline async onClick handler should be
replaced to call runAsynchronouslyWithAlert with an async callback that contains
the existing logic (the checks for stripe/elements, const card =
elements.getElement(CardElement), stripe.confirmCardSetup call, result/error
handling, and props.onSetupIntentSucceeded), so errors are surfaced via the
shared alert mechanism; preserve all setErrorMessage calls and early returns
inside that async callback and pass the async function (not a promise) to
runAsynchronouslyWithAlert where the onClick currently is.
🧹 Nitpick comments (7)
apps/backend/src/lib/payments.tsx (2)
456-458: Consider using parameterized query pattern for Stripe search.The
customerIdis directly interpolated into the Stripe search query string. While the upstreamensureCustomerExistsvalidates it's a UUID (which contains only safe characters), direct string interpolation in queries is generally discouraged. Stripe's search API doesn't support parameterized queries, but this is worth noting for awareness.
461-480: Fallback pagination could be slow for accounts with many customers.The fallback loop iterates up to 10 pages (1000 customers) when Stripe's search returns no results due to eventual consistency. For accounts with many customers, this could introduce latency. Consider adding a log or metric when this fallback path is taken to monitor its frequency.
packages/template/src/components-page/account-settings/payments/payments-page.tsx (1)
8-36: Consider simplifying the conditional logic.The component correctly handles both mock and real modes. However, the
if (!user)check on lines 23-25 appears to be unreachable. Whenor: "redirect"is used (line 10 in non-mock mode),useUserwill redirect and suspend rather than returnnull. Since mock mode is handled by the early return (lines 12-21), the null check is defensive but unnecessary.♻️ Simplified implementation
export function PaymentsPage(props: { mockMode?: boolean }) { const { t } = useTranslation(); - const user = useUser({ or: props.mockMode ? "return-null" : "redirect" }); if (props.mockMode) { return ( <PageLayout> <PaymentsPanel title={t("Personal payments")} mockMode /> </PageLayout> ); } - if (!user) { - return null; - } + const user = useUser({ or: "redirect" }); return ( <PageLayout> <PaymentsPanel title={t("Personal payments")} customer={user} customerType="user" /> </PageLayout> ); }apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.ts (1)
10-31: Avoidanytypes forfullReqandtenancyparameters.The
anytypes onfullReqandtenancybypass type safety. Consider extracting proper types from the smart route handler's request schema or using the actual Tenancy type from prisma-client.Also, this
ensureClientCanAccessCustomerhelper is duplicated across at least three route files (billing,setup-intent,set-default). Consider extracting it to a shared module (e.g.,@/lib/paymentsor@/lib/request-checks).apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts (1)
87-94: Redundant stripeAccountId check.
getStripeForAccount({ tenancy: auth.tenancy })on line 69 already throws ifstripeAccountIdis missing (seeapps/backend/src/lib/stripe.tsxlines 47-50). This check on lines 87-94 is unreachable—if payments weren't set up, line 69 would have already failed.Consider either:
- Removing this redundant check, or
- Moving the check before the Stripe calls to fail fast with a cleaner 400 error
Option: Check early and avoid redundancy
handler: async ({ auth, params }, fullReq) => { if (auth.type === "client") { await ensureClientCanAccessCustomer( { customer_type: params.customer_type, customer_id: params.customer_id }, fullReq, auth.tenancy, ); } + const project = await globalPrismaClient.project.findUnique({ + where: { id: auth.tenancy.project.id }, + select: { stripeAccountId: true }, + }); + const stripeAccountId = project?.stripeAccountId; + if (!stripeAccountId) { + throw new StatusError(400, "Payments are not set up in this Stack Auth project."); + } + const prisma = await getPrismaClientForTenancy(auth.tenancy); - const stripe = await getStripeForAccount({ tenancy: auth.tenancy }); + const stripe = await getStripeForAccount({ accountId: stripeAccountId }); // ... rest of handler ... - const project = await globalPrismaClient.project.findUnique({ - where: { id: auth.tenancy.project.id }, - select: { stripeAccountId: true }, - }); - const stripeAccountId = project?.stripeAccountId; - if (!stripeAccountId) { - throw new StatusError(400, "Payments are not set up in this Stack Auth project."); - } return { statusCode: 200, bodyType: "json", body: { client_secret: setupIntent.client_secret, - stripe_account_id: stripeAccountId, + stripe_account_id: stripeAccountId, }, }; },packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
12-56: Consider importing types fromcustomers/index.tsinstead of duplicating.These type definitions (
PaymentMethodSummary,CustomerBilling,CustomerPaymentMethodSetupIntent,CustomerLike) largely duplicate types frompackages/template/src/lib/stack-app/customers/index.ts. Consider importing and reusing to avoid drift.packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
1625-1672: The cache inefficiency concern is valid but represents a minor design trade-off rather than a critical issue.When
sessionis null, a neweffectiveSessionobject is created each time_createCustomeris called. Since the cache key includeseffectiveSessionand cache keys are compared by object identity (via WeakMap), each null-session call results in a separate cache entry. This means billing data won't be cached across multiple Customer instances for null sessions.This is likely intentional:
- Server-side usage (where null sessions occur): The Customer objects are created once per User/Team, so the caching inefficiency has minimal impact
- Client-side usage: Always provides a real session, so this doesn't apply
- Backend enforcement: Access control is enforced server-side regardless of session state
The pattern
session ?? createSession(...)differs from the defensive?? throwErr(...)pattern in the codebase, but creating a fallback session is acceptable here since billing operations require proper authentication at the backend layer.Optional improvement: Cache a singleton anonymous session at the app level to reuse for all null cases, improving cache efficiency. However, this is a low-priority optimization.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tsapps/e2e/tests/snapshot-serializer.tspackages/react/package.jsonpackages/stack-shared/src/interface/client-interface.tspackages/stack-shared/src/interface/crud/products.tspackages/stack-shared/src/interface/server-interface.tspackages/stack/package.jsonpackages/template/package-template.jsonpackages/template/package.jsonpackages/template/src/components-page/account-settings.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsxpackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxpackages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/template/src/lib/stack-app/customers/index.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors, never use
toast; instead, use alerts as toasts are easily missed by the user
Files:
packages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tspackages/template/src/components-page/account-settings/payments/payments-page.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tspackages/stack-shared/src/interface/crud/products.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tspackages/template/src/lib/stack-app/customers/index.tsapps/e2e/tests/snapshot-serializer.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tspackages/stack-shared/src/interface/server-interface.tspackages/stack-shared/src/interface/client-interface.tsapps/backend/src/lib/payments.tsx
**/*.{tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g.,transition-colors hover:transition-none)
Files:
packages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsxpackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use
usePathnameinstead ofawait params)
Files:
packages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tspackages/template/src/components-page/account-settings/payments/payments-page.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tspackages/stack-shared/src/interface/crud/products.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tspackages/template/src/lib/stack-app/customers/index.tsapps/e2e/tests/snapshot-serializer.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tspackages/stack-shared/src/interface/server-interface.tspackages/stack-shared/src/interface/client-interface.tsapps/backend/src/lib/payments.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
packages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tspackages/template/src/components-page/account-settings/payments/payments-page.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tspackages/stack-shared/src/interface/crud/products.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tspackages/template/src/lib/stack-app/customers/index.tsapps/e2e/tests/snapshot-serializer.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tspackages/stack-shared/src/interface/server-interface.tspackages/stack-shared/src/interface/client-interface.tsapps/backend/src/lib/payments.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Code defensively; prefer?? throwErr(...)over non-null assertions with good error messages explicitly stating violated assumptions
Avoid theanytype; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime
Files:
packages/template/src/components-page/account-settings/teams/team-payments-section.tsxpackages/template/src/components-page/account-settings/teams/team-page.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tspackages/template/src/components-page/account-settings/payments/payments-page.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tspackages/stack-shared/src/interface/crud/products.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tspackages/template/src/lib/stack-app/customers/index.tsapps/e2e/tests/snapshot-serializer.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tspackages/stack-shared/src/interface/server-interface.tspackages/stack-shared/src/interface/client-interface.tsapps/backend/src/lib/payments.tsx
**/e2e/**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry
Files:
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
When writing tests, prefer
.toMatchInlineSnapshot()over other selectors if possible; check snapshot-serializer.ts to understand how snapshots are formatted and how non-deterministic values are handled
Files:
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
🧠 Learnings (6)
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to {packages/stack,packages/js}/**/*.{ts,tsx,js,jsx} : NEVER UPDATE packages/stack OR packages/js; instead, update packages/template as those packages are copies of it
Applied to files:
packages/template/package-template.jsonpackages/template/package.jsonpackages/stack/package.json
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: The project uses Next.js with App Router framework for the backend, dashboard, and dev-launchpad apps
Applied to files:
packages/template/package-template.json
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/e2e/**/*.{test,spec}.{ts,tsx,js,jsx} : ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry
Applied to files:
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/config/schema.ts,**/config/**/*.{ts,tsx} : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`
Applied to files:
packages/stack-shared/src/interface/crud/products.ts
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
packages/template/src/components-page/account-settings.tsx
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : When writing tests, prefer `.toMatchInlineSnapshot()` over other selectors if possible; check snapshot-serializer.ts to understand how snapshots are formatted and how non-deterministic values are handled
Applied to files:
apps/e2e/tests/snapshot-serializer.ts
🧬 Code graph analysis (12)
packages/template/src/components-page/account-settings/teams/team-payments-section.tsx (4)
packages/template/src/lib/translations.tsx (1)
useTranslation(4-19)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
useUser(1992-2034)packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
PaymentsPanel(112-125)packages/template/src/components-page/account-settings/section.tsx (1)
Section(3-22)
packages/template/src/components-page/account-settings/teams/team-page.tsx (1)
packages/template/src/components-page/account-settings/teams/team-payments-section.tsx (1)
TeamPaymentsSection(9-34)
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts (2)
apps/e2e/tests/helpers.ts (1)
it(12-12)apps/e2e/tests/backend/backend-helpers.ts (1)
niceBackendFetch(109-173)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts (7)
packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)apps/backend/src/prisma-client.tsx (1)
getPrismaClientForTenancy(67-69)apps/backend/src/lib/request-checks.tsx (1)
ensureUserTeamPermissionExists(82-115)apps/backend/src/route-handlers/smart-route-handler.tsx (1)
createSmartRouteHandler(216-301)packages/stack-shared/src/schema-fields.ts (3)
clientOrHigherAuthTypeSchema(534-534)adaptSchema(330-330)yupString(187-190)apps/backend/src/lib/stripe.tsx (1)
getStripeForAccount(29-54)apps/backend/src/lib/payments.tsx (1)
ensureStripeCustomerForCustomer(493-511)
packages/template/src/components-page/account-settings/payments/payments-page.tsx (3)
packages/template/src/lib/translations.tsx (1)
useTranslation(4-19)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
useUser(1992-2034)packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
PaymentsPanel(112-125)
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.ts (5)
apps/backend/src/prisma-client.tsx (1)
getPrismaClientForTenancy(67-69)apps/backend/src/lib/request-checks.tsx (1)
ensureUserTeamPermissionExists(82-115)packages/stack-shared/src/schema-fields.ts (3)
clientOrHigherAuthTypeSchema(534-534)adaptSchema(330-330)yupString(187-190)apps/backend/src/lib/stripe.tsx (1)
getStripeForAccount(29-54)apps/backend/src/lib/payments.tsx (2)
getStripeCustomerForCustomerOrNull(435-491)getDefaultCardPaymentMethodSummary(521-540)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (8)
packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)apps/backend/src/prisma-client.tsx (1)
getPrismaClientForTenancy(67-69)apps/backend/src/lib/request-checks.tsx (1)
ensureUserTeamPermissionExists(82-115)apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts (1)
POST(33-105)apps/backend/src/route-handlers/smart-route-handler.tsx (1)
createSmartRouteHandler(216-301)packages/stack-shared/src/schema-fields.ts (5)
yupObject(247-251)clientOrHigherAuthTypeSchema(534-534)adaptSchema(330-330)yupString(187-190)yupNumber(191-194)apps/backend/src/lib/stripe.tsx (1)
getStripeForAccount(29-54)apps/backend/src/lib/payments.tsx (2)
ensureStripeCustomerForCustomer(493-511)getDefaultCardPaymentMethodSummary(521-540)
packages/template/src/lib/stack-app/customers/index.ts (1)
packages/template/src/lib/stack-app/common.ts (1)
AsyncStoreProperty(9-11)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (2)
packages/template/src/lib/stack-app/apps/implementations/common.ts (2)
createCacheBySession(36-46)useAsyncCache(162-213)packages/template/src/lib/stack-app/customers/index.ts (3)
CustomerBilling(72-75)CustomerPaymentMethodSetupIntent(77-80)CustomerDefaultPaymentMethod(64-70)
packages/stack-shared/src/interface/server-interface.ts (2)
packages/stack-shared/src/sessions.ts (1)
InternalSession(81-288)packages/stack-shared/src/utils/urls.tsx (1)
urlString(314-316)
packages/stack-shared/src/interface/client-interface.ts (2)
packages/stack-shared/src/sessions.ts (1)
InternalSession(81-288)packages/stack-shared/src/utils/urls.tsx (1)
urlString(314-316)
apps/backend/src/lib/payments.tsx (3)
apps/backend/src/prisma-client.tsx (1)
PrismaClientTransaction(25-27)packages/template/src/lib/stack-app/customers/index.ts (1)
Customer(82-117)packages/stack-shared/src/utils/errors.tsx (1)
StackAssertionError(69-85)
🪛 Biome (2.1.2)
packages/template/package-template.json
[error] 89-89: The key // was already declared.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Vercel Agent Review
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: docker
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: setup-tests-with-custom-base-port
- GitHub Check: all-good
🔇 Additional comments (43)
apps/e2e/tests/snapshot-serializer.ts (1)
73-73: LGTM! Correctly strips non-deterministic subscription timestamp.Adding
current_period_endto the strip list is the right approach for handling this subscription period timestamp field, ensuring test snapshots remain deterministic and consistent with the existing pattern of stripping timestamp values.Based on learnings, this aligns well with the preference for
.toMatchInlineSnapshot()testing by ensuring non-deterministic values are properly serialized.packages/react/package.json (1)
61-62: LGTM!The Stripe dependencies are correctly added and align with the template configuration.
packages/template/package-template.json (1)
89-92: LGTM!The Stripe dependencies are correctly scoped to
react-likeplatforms using the established templating pattern. The static analysis warning about duplicate"//"keys is a false positive—this is the intentional platform-conditional templating mechanism used throughout this file.packages/template/package.json (1)
66-67: LGTM!Stripe dependencies are correctly added to the template package.
apps/backend/src/lib/payments.tsx (1)
759-771: LGTM!The
subscriptionfield extension toOwnedProductis well-designed. TheisCancelablelogic correctly identifies that only subscriptions with both a database ID and product ID can be canceled (excluding default catalog subscriptions and inline products).packages/stack/package.json (1)
61-62: LGTM!The Stripe dependencies are correctly propagated from the template. Based on learnings, this file is auto-generated from
packages/template, so these changes should be the result of template generation.packages/template/src/components-page/account-settings/teams/team-page.tsx (2)
10-10: LGTM!The import is correctly added.
21-21: LGTM!The
TeamPaymentsSectionis correctly integrated into the page layout with the appropriate key prop pattern.packages/template/src/components-page/account-settings/teams/team-payments-section.tsx (2)
9-22: LGTM!The component correctly checks for team admin permissions before rendering the PaymentsPanel. The permission check pattern using
user.usePermissionis consistent with the codebase's established patterns.
24-33: LGTM!The fallback UI for non-admin users provides clear messaging about the required permissions, following the established
Sectioncomponent pattern.apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (1)
130-135: LGTM! Comprehensive test coverage for new subscription fields.The test snapshots have been correctly updated to reflect the new API shape with
typeandsubscriptionfields. The coverage properly distinguishes between subscription products (with subscription metadata) and one-time purchases (withsubscription: null), and spans all critical scenarios including granting, listing, canceling, server-only products, stackable products, inline products, and pagination.Based on learnings, this aligns with the requirement to add E2E tests when changing API interfaces.
Also applies to: 530-535, 666-671, 744-749, 1049-1054, 1070-1071, 1191-1196, 1229-1230, 1246-1247
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts (4)
4-22: LGTM! Proper coverage for the no-customer scenario.The test correctly verifies that when payments are not set up, the billing endpoint returns
has_customer: falsewith a null default payment method.
24-68: LGTM! Comprehensive flow testing for authenticated billing access.The test properly verifies the complete flow: initial billing status, setup-intent creation with proper field validation, and billing status after setup. The use of
toMatchObjectfor the setup-intent response is appropriate since the exact values are non-deterministic.
70-92: LGTM! Proper authorization checks in place.The test correctly verifies that a signed-in user cannot read another user's billing information (403), while being able to read their own (200). This validates the tenancy-based access control.
94-137: LGTM! Thorough team permission validation.The test properly validates that team admins can manage team billing while regular members cannot, with appropriate error structure including
permission_id,team_id, anduser_idin the response details.Based on learnings, this test suite provides excellent coverage for the new billing API endpoints.
packages/stack-shared/src/interface/crud/products.ts (1)
8-13: LGTM! Schema properly extended for subscription metadata.The schema additions correctly introduce:
- A
typediscriminator field to distinguish between one-time and subscription products- A
subscriptionfield (nullable) to carry subscription-specific metadata like cancellation status and period informationThis aligns with the backend implementation and test expectations.
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (1)
57-62: LGTM! Backend implementation correctly transforms subscription data.The implementation properly:
- Adds the
typefield from the product model- Conditionally maps subscription metadata when present, with proper date conversion to ISO string
- Returns
nullfor the subscription field when the product has no subscriptionThis aligns with the shared schema and test expectations.
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.ts (3)
64-71: Server/admin auth types bypass customer access checks.The handler only validates access when
auth.type === "client". Confirm this is intentional—server/admin keys can access any customer's billing info without ownership verification.
73-87: LGTM: Graceful handling when payments not configured.Returning
has_customer: falsewhenstripeAccountIdis missing is a clean approach that allows clients to handle this state gracefully.
89-123: LGTM: Stripe customer lookup and response.The flow correctly handles the case where no Stripe customer exists, and the response structure aligns with the declared schema.
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (3)
78-84: Verify behavior:ensureStripeCustomerForCustomercreates customer if missing.This endpoint will create a Stripe customer if one doesn't exist (per
ensureStripeCustomerForCustomerinapps/backend/src/lib/payments.tsx). Confirm this is desired—it differs from the billing GET endpoint which returnshas_customer: falsewhen no customer exists.
86-95: LGTM: Thorough setup intent validation.Good defensive checks: verifying customer ownership, succeeded status, and presence of payment method before proceeding.
103-106: LGTM: Handles deleted customer edge case.Checking
updatedCustomer.deletedafter the update protects against a race condition where the customer is deleted between update and retrieve.apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts (1)
78-85: LGTM: Setup intent creation.Correctly creates setup intent with
off_sessionusage for future charges and validates client_secret is returned.packages/template/src/components-page/account-settings.tsx (3)
83-91: LGTM: Mock billing and conditional rendering setup.Clean integration of billing data with proper fallback for mock mode and conditional rendering based on
hasCustomer.
146-154: LGTM: Payments section integration.Proper Suspense boundary with skeleton fallback, consistent with other sections like API Keys.
241-248: LGTM: PaymentsPageSkeleton.Skeleton layout is consistent with other page skeletons in the file.
packages/template/src/lib/stack-app/customers/index.ts (3)
42-47: LGTM: Extended CustomerProduct type.Clean extension with subscription metadata that enables proper UI rendering of renewal dates and cancel actions.
64-80: LGTM: New billing-related types.Well-structured types that align with the backend API response schemas.
90-100: LGTM: Customer interface extensions.New methods and billing store property follow existing patterns in the codebase.
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (2)
189-194: VerifyNEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEYis documented.The Stripe publishable key is read from an environment variable. Ensure this is documented in setup instructions, as missing it will silently disable payment method updates (stripePromise becomes null).
304-326: LGTM: Subscription cancellation flow.Proper confirmation dialog with danger styling and async handling inside
okButton.onClick.packages/stack-shared/src/interface/server-interface.ts (3)
61-81: LGTM:getCustomerBillingimplementation.Follows existing patterns in the interface, properly uses
urlStringfor URL encoding, and return type matches the backend schema.
83-103: LGTM:createCustomerPaymentMethodSetupIntentimplementation.Correctly sends POST with empty JSON body and appropriate headers.
105-133: LGTM:setDefaultCustomerPaymentMethodFromSetupIntentimplementation.Properly serializes the setup_intent_id in the request body.
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (4)
272-285: LGTM: Billing cache properly structured.The billing cache follows the established pattern using
createCacheBySessionwith appropriate dependencies and type safety.
1163-1179: LGTM: Product mapping correctly extended with subscription data.The new
typeandsubscriptionfields are properly mapped with appropriate null handling and date conversion.
1181-1195: LGTM: Billing response mapper is correctly implemented.The helper properly transforms the backend response format to the public
CustomerBillingtype.
1630-1651: LGTM: Billing methods correctly implemented with proper caching.The new billing methods follow established patterns:
- Cache usage for reads (
getBilling,useBilling)- Proper cache refresh after mutations (
setDefaultPaymentMethodFromSetupIntent)- Consistent use of
effectiveSessionacross all billing operations- Type-safe return values matching public interfaces
packages/stack-shared/src/interface/client-interface.ts (4)
1851-1871: LGTM: Billing retrieval method correctly implemented.The
getCustomerBillingmethod follows established patterns with safe URL construction and appropriate return types.
1873-1893: LGTM: Setup intent creation properly implemented.The method correctly creates a Stripe SetupIntent for adding payment methods. The empty request body is appropriate since all necessary information (customer type and ID) is in the URL path.
1895-1923: LGTM: Default payment method setter correctly implemented.The method properly sets the default payment method from a SetupIntent with appropriate parameter passing and return type.
1851-1923: No changes needed. The customer type restriction is intentional and correctly enforced.The billing methods (
getCustomerBilling,createCustomerPaymentMethodSetupIntent,setDefaultCustomerPaymentMethodFromSetupIntent) properly restrict to"user" | "team"because billing/payment management requires authenticated customers. Custom customers are designed for external/unregistered access to products only. This design is consistently enforced at the backend layer with explicit access control checks ("Clients can only manage their own billing."). The type difference between billing methods and product operations is correct by design.
...c/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts
Outdated
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The CustomerType enum is used in the code but not imported, which will cause a compilation error.
View Details
📝 Patch Details
diff --git a/apps/backend/src/lib/payments.tsx b/apps/backend/src/lib/payments.tsx
index f4aa0958..a7b1a57c 100644
--- a/apps/backend/src/lib/payments.tsx
+++ b/apps/backend/src/lib/payments.tsx
@@ -1,5 +1,5 @@
import { PrismaClientTransaction } from "@/prisma-client";
-import { PurchaseCreationSource, SubscriptionStatus } from "@/generated/prisma/client";
+import { PurchaseCreationSource, SubscriptionStatus, CustomerType } from "@/generated/prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import type { inlineProductSchema, productSchema, productSchemaWithMetadata } from "@stackframe/stack-shared/dist/schema-fields";
import { SUPPORTED_CURRENCIES } from "@stackframe/stack-shared/dist/utils/currency-constants";
Analysis
Missing CustomerType import in payments.tsx
What fails: TypeScript compilation error - CustomerType enum used but not imported in customerTypeToStripeCustomerType() function at lines 431-432
How to reproduce:
cd apps/backend
pnpm install
STACK_DATABASE_CONNECTION_STRING="placeholder" pnpm run prisma generate
pnpm run typecheckResult:
src/lib/payments.tsx(431,75): error TS2304: Cannot find name 'CustomerType'.
src/lib/payments.tsx(432,36): error TS2552: Cannot find name 'CustomerType'. Did you mean 'customerType'?
src/lib/payments.tsx(432,56): error TS2552: Cannot find name 'CustomerType'. Did you mean 'customerType'?
Expected: TypeScript compilation should pass. The CustomerType enum is defined in the Prisma schema and generated in @/generated/prisma/client, but the import statement on line 2 does not include it despite importing PurchaseCreationSource and SubscriptionStatus from the same module.
Fix: Add CustomerType to the import statement on line 2:
import { PurchaseCreationSource, SubscriptionStatus, CustomerType } from "@/generated/prisma/client";
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In
@packages/template/src/components-page/account-settings/payments/payments-page.tsx:
- Around line 24-30: The hook calls are placed after an early return causing a
Rules of Hooks violation; move the call to user.useTeams() and the useState hook
for selectedTeam so they execute unconditionally before the if (!user) return
null; check (i.e., call user.useTeams(), compute hasTeams, and call
useState<Team | null>(...) for selectedTeam/setSelectedTeam above the
conditional return) to ensure all hooks run in the same order on every render.
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Line 65: The expression assigning darkMode accesses
document.documentElement.style during render which breaks SSR; update this by
guarding for a browser environment (e.g., check typeof window !== "undefined" or
typeof document !== "undefined") before reading document, or move the detection
into a useEffect and store the result in state (create a darkMode state and set
it inside useEffect) so no document access happens during server rendering;
adjust any consumers of darkMode accordingly (the variable name darkMode and its
assignment should be replaced with the guarded or effect-based logic).
- Around line 80-107: The Button's async onClick handler in PaymentsPanel is
being used directly; wrap the async logic with runAsynchronouslyWithAlert to
ensure consistent error handling and UI alerts. Replace the inline async arrow
passed to Button.onClick with a call to runAsynchronouslyWithAlert(() => { ...
}) containing the existing logic that references
elements.getElement(CardElement), stripe.confirmCardSetup(props.clientSecret,
...), setErrorMessage(...), and the final await
props.onSetupIntentSucceeded(result.setupIntent.id); ensure you return the
Promise from runAsynchronouslyWithAlert so Button.onClick receives a function
(not a bare async handler).
- Line 291: Replace the non-null assertion product.id! passed into
setCancelProductId with a defensive check that throws on null/undefined per
guidelines; e.g., use product.id ?? throwErr("Missing product id for
cancellation") when calling setCancelProductId so setCancelProductId receives a
guaranteed non-null value and avoids the `!` operator, referencing the
setCancelProductId call site and product.id.
- Around line 311-323: The dialog okButton's async onClick handler uses raw
await calls and should be wrapped with runAsynchronouslyWithAlert for consistent
error handling; replace the current onClick async function (which references
cancelProductId, props.customerType, stackApp.cancelSubscription, and
setCancelProductId) with a call like onClick: () =>
runAsynchronouslyWithAlert(async () => { const productId = cancelProductId; if
(!productId) return; if (props.customerType === "team") { await
stackApp.cancelSubscription({ teamId: props.customer.id, productId }); } else {
await stackApp.cancelSubscription({ productId }); } setCancelProductId(null);
}); and ensure runAsynchronouslyWithAlert is imported where payments-panel.tsx
defines okButton.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsxpackages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors, never use
toast; instead, use alerts as toasts are easily missed by the user
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsx
**/*.{tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g.,transition-colors hover:transition-none)
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use
usePathnameinstead ofawait params)
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Code defensively; prefer?? throwErr(...)over non-null assertions with good error messages explicitly stating violated assumptions
Avoid theanytype; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsxapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/components-page/account-settings.tsx
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧬 Code graph analysis (3)
packages/template/src/components-page/account-settings.tsx (2)
packages/template/src/components-page/account-settings/payments/payments-page.tsx (1)
PaymentsPage(10-52)packages/template/src/components-page/account-settings/page-layout.tsx (1)
PageLayout(1-7)
apps/backend/src/lib/payments.tsx (3)
apps/backend/src/prisma-client.tsx (1)
PrismaClientTransaction(25-27)packages/template/src/lib/stack-app/customers/index.ts (1)
Customer(82-117)packages/stack-shared/src/utils/errors.tsx (1)
StackAssertionError(69-85)
packages/template/src/components-page/account-settings/payments/payments-page.tsx (3)
packages/template/src/lib/translations.tsx (1)
useTranslation(4-19)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
useUser(1992-2034)packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
PaymentsPanel(112-125)
🪛 Biome (2.1.2)
packages/template/src/components-page/account-settings/payments/payments-page.tsx
[error] 28-28: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 30-30: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests-with-custom-base-port
- GitHub Check: setup-tests
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: restart-dev-and-test
- GitHub Check: lint_and_build (latest)
- GitHub Check: docker
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: all-good
- GitHub Check: Vercel Agent Review
🔇 Additional comments (2)
packages/template/src/components-page/account-settings.tsx (1)
4-4: LGTM!The integration of the Payments section follows the existing patterns correctly, with proper Suspense boundaries, skeleton loading states, and mock data support.
Also applies to: 16-16, 28-29, 85-85, 90-90, 145-153, 240-247
apps/backend/src/lib/payments.tsx (1)
3-3: LGTM!The backend payment utilities are well-implemented with proper error handling, defensive coding, and type safety. The Stripe customer resolution logic correctly handles eventual consistency with a search-then-paginate fallback strategy.
Also applies to: 269-269, 306-306, 335-335, 354-354, 432-541, 767-772, 819-823, 836-836
packages/template/src/components-page/account-settings/payments/payments-page.tsx
Outdated
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Outdated
Show resolved
Hide resolved
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @apps/backend/src/lib/payments.tsx:
- Around line 561-564: The code calls
options.stripe.paymentMethods.retrieve(defaultPaymentMethodId) which will throw
if the payment method was deleted; wrap that call in a try/catch around the
retrieval in the same function, catch Stripe errors (or any error) and return
null when retrieval fails, otherwise continue to check pm.type and pm.card as
before; reference the retrieve call (options.stripe.paymentMethods.retrieve),
the defaultPaymentMethodId variable, and the subsequent pm.type/pm.card check so
you modify the exact block to handle missing/deleted payment methods gracefully.
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Around line 312-324: The okButton.onClick async handler currently calls
stackApp.cancelSubscription directly; update it to wrap the cancellation logic
in runAsynchronouslyWithAlert by passing an async function that grabs
cancelProductId, returns early if missing, calls stackApp.cancelSubscription
with { teamId: props.customer.id, productId } when props.customerType === "team"
or { productId } otherwise, and on success calls setCancelProductId(null); this
preserves existing behavior but ensures errors are surfaced via
runAsynchronouslyWithAlert.
- Around line 81-108: The onClick handler on the Save payment method Button uses
an inline async function with manual error handling; replace it by wrapping the
async flow in runAsynchronouslyWithAlert from
@stackframe/stack-shared/dist/utils/promises so errors surface via the app alert
system. Move the existing logic (checks for stripe/elements,
elements.getElement(CardElement), calling stripe.confirmCardSetup, setting
inline validation via setErrorMessage, and calling props.onSetupIntentSucceeded)
into a function passed to runAsynchronouslyWithAlert, keeping setErrorMessage
for client-side validation but removing manual catch/alert logic so
runAsynchronouslyWithAlert handles exceptions and displays alerts consistently.
- Line 66: The line that reads const darkMode = "color-scheme" in
document.documentElement.style && document.documentElement.style["color-scheme"]
=== "dark" is SSR-unsafe because it accesses document during render; change it
to a client-side-only value by introducing a React state (e.g., darkMode via
useState) and set its value inside a useEffect (or compute it in useMemo inside
useEffect) so the document access runs only on the client; update any usages of
darkMode in the component to read from that state (reference the constant and
component payments-panel.tsx and the darkMode symbol to locate the change).
🧹 Nitpick comments (4)
apps/backend/src/lib/payments.tsx (2)
482-486: Clarify the backward compatibility behavior with a comment.The
matchesCustomerfunction returnstruewhenstoredTypeis missing, which appears to be intentional for backward compatibility with customers created beforecustomerTypewas added to metadata. Consider adding a brief comment to explain this.📝 Suggested clarification
const matchesCustomer = (customer: Stripe.Customer) => { const storedType = customer.metadata.customerType; - if (!storedType) return true; + if (!storedType) return true; // Backward compatibility: match customers created before customerType was added return storedType === stripeCustomerType; };
494-512: Consider logging when fallback to listing is used.The fallback loop handles Stripe's eventual consistency, but silently iterating through customer lists could mask issues in production. Consider adding observability (e.g., a structured log or metric) when the search fails and the fallback is triggered.
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.ts (1)
49-66: Consider using consistent Stripe initialization pattern.This route fetches
stripeAccountIdmanually and passes it togetStripeForAccount, while the setup-intent route usesgetStripeForAccount({ tenancy: auth.tenancy }). The current approach is slightly more efficient but creates inconsistency. Consider standardizing on one pattern for maintainability.packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
30-38: Import types instead of duplicating them.The
CustomerBillingandCustomerPaymentMethodSetupIntenttypes are duplicated frompackages/template/src/lib/stack-app/customers/index.ts. Import them directly to avoid maintenance issues if the source types change.♻️ Refactor to import types
Add to imports:
+import { CustomerBilling, CustomerPaymentMethodSetupIntent } from "../../../lib/stack-app/customers";Remove the duplicate type definitions:
-type CustomerBilling = { - hasCustomer: boolean, - defaultPaymentMethod: PaymentMethodSummary, -}; - -type CustomerPaymentMethodSetupIntent = { - clientSecret: string, - stripeAccountId: string, -};
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tsapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.tsapps/backend/src/lib/payments.tsxpackages/template/src/components-page/account-settings/payments/payments-page.tsxpackages/template/src/components-page/account-settings/payments/payments-panel.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/template/src/components-page/account-settings/payments/payments-page.tsx
- apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors, never use
toast; instead, use alerts as toasts are easily missed by the user
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
**/*.{tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g.,transition-colors hover:transition-none)
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxapps/backend/src/lib/payments.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use
usePathnameinstead ofawait params)
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Code defensively; prefer?? throwErr(...)over non-null assertions with good error messages explicitly stating violated assumptions
Avoid theanytype; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime
Files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsxapps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxapps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
🧠 Learnings (3)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2026-01-07T00:55:19.871Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.871Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧬 Code graph analysis (2)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (4)
packages/template/src/lib/stack-app/customers/index.ts (2)
CustomerBilling(72-75)CustomerPaymentMethodSetupIntent(77-80)packages/template/src/components-page/account-settings/section.tsx (1)
Section(3-22)packages/stack-shared/src/utils/promises.tsx (1)
runAsynchronouslyWithAlert(312-328)apps/e2e/tests/js/convex.test.ts (1)
open(31-33)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (7)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/setup-intent/route.ts (1)
POST(8-82)apps/backend/src/route-handlers/smart-route-handler.tsx (1)
createSmartRouteHandler(216-301)packages/stack-shared/src/schema-fields.ts (5)
yupObject(247-251)clientOrHigherAuthTypeSchema(534-534)adaptSchema(330-330)yupString(187-190)yupNumber(191-194)apps/backend/src/lib/payments.tsx (3)
ensureClientCanAccessCustomer(25-52)ensureStripeCustomerForCustomer(525-543)getDefaultCardPaymentMethodSummary(553-572)apps/backend/src/prisma-client.tsx (1)
getPrismaClientForTenancy(67-69)apps/backend/src/lib/stripe.tsx (1)
getStripeForAccount(29-54)packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Vercel Agent Review
- GitHub Check: setup-tests-with-custom-base-port
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: build (22.x)
- GitHub Check: docker
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: all-good
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: setup-tests
🔇 Additional comments (9)
apps/backend/src/lib/payments.tsx (2)
25-52: LGTM! Access control logic is well-structured.The
ensureClientCanAccessCustomerfunction correctly enforces user authentication and authorization. For user customers, it validates ownership; for team customers, it delegates to the existing permission check withteam_adminrequirement.
798-803: LGTM! Well-designed subscription metadata in OwnedProduct.The
subscriptionfield cleanly encapsulates period end, cancel status, and cancelability. TheisCancelablelogic correctly requires bothidandproductIdto be non-null, preventing cancellation of default/synthetic subscriptions.Also applies to: 850-854
apps/backend/src/app/api/latest/payments/billing/[customer_type]/[customer_id]/route.ts (2)
38-47: LGTM! Proper access control for client requests.The handler correctly enforces that clients can only access their own billing information, with appropriate forbidden message.
75-98: LGTM! Clear response structure with proper state distinction.The response correctly distinguishes between "no Stripe customer exists" (
has_customer: false) and "customer exists but has no default payment method" (has_customer: true,default_payment_method: null).apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (4)
42-51: LGTM! Consistent access control with other payment routes.The client access validation correctly mirrors the pattern used in the setup-intent and billing routes.
63-72: LGTM! Robust setup intent validation.The handler correctly validates: (1) the setup intent belongs to this customer, (2) the intent succeeded, and (3) a payment method is attached. This prevents IDOR attacks and ensures data integrity.
55-66: Verify the customer creation behavior is intentional.
ensureStripeCustomerForCustomercreates a new Stripe customer if one doesn't exist. In the normal flow (setup-intent → set-default), this is fine since setup-intent already creates the customer. However, if called out-of-order with a mismatched setup intent, the validation at line 64 will correctly fail.
80-97: LGTM! Proper handling of edge cases in response generation.The handler correctly checks for unexpected customer deletion and returns the payment method summary. The nullable
default_payment_methodin the response schema correctly handles non-card payment methods.packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
190-204: Good use of defensive coding and async handling.The null checks for
setupIntentStripeAccountIdandpublishableKey, combined with proper use ofrunAsynchronouslyWithAlertfor the async operation, demonstrate good defensive programming practices.
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
Show resolved
Hide resolved
Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @packages/template/package-template.json:
- Around line 90-93: The package-template.json pins outdated Stripe packages;
update the dependency versions for "@stripe/react-stripe-js" to ^5.4.0 and
"@stripe/stripe-js" to ^8.6.0 in the package-template.json so the template uses
the current compatible releases (replace the existing version strings for
"@stripe/react-stripe-js" and "@stripe/stripe-js" entries).
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Line 66: The inline read of document.documentElement.style into the darkMode
variable in the PaymentsPanel component causes DOM access during render and can
hydrate mismatches; change this to a client-safe pattern by replacing the direct
access with a state value (e.g., darkMode state) and populate it inside a
useEffect (or consume your theme context) so the DOM is only read on the client
after mount; update references to the darkMode variable accordingly and ensure
initial server-rendered value is stable (false or undefined) to avoid hydration
issues.
- Around line 30-38: The local type CustomerPaymentMethodSetupIntent duplicates
the exported type from the customers module; remove the local declaration and
import CustomerPaymentMethodSetupIntent from the customers module instead
(replace the local type declaration with an import). Also review CustomerBilling
vs the customers' CustomerDefaultPaymentMethod/PaymentMethodSummary usage:
confirm whether CustomerBilling should continue using PaymentMethodSummary or be
aligned to CustomerDefaultPaymentMethod and update the type or import
accordingly to avoid drift.
🧹 Nitpick comments (2)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
272-285: Consider extracting the response type to avoid duplication.The billing response type is defined inline here and again in
_customerBillingFromResponse(lines 1181-1190). Consider extracting it to a shared type alias for maintainability.♻️ Suggested refactor
+type CustomerBillingResponse = { + has_customer: boolean, + default_payment_method: { + id: string, + brand: string | null, + last4: string | null, + exp_month: number | null, + exp_year: number | null, + } | null, +}; + -private readonly _customerBillingCache = createCacheBySession<["user" | "team", string], { - has_customer: boolean, - default_payment_method: { - id: string, - brand: string | null, - last4: string | null, - exp_month: number | null, - exp_year: number | null, - } | null, -}>( +private readonly _customerBillingCache = createCacheBySession<["user" | "team", string], CustomerBillingResponse>(Then update
_customerBillingFromResponseto useCustomerBillingResponseas its parameter type.packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
310-318: Consider usingthrowErrfor defensive coding.Per coding guidelines, use
?? throwErr(...)instead of silent returns. Theif (!productId) return;pattern could mask bugs.💡 Suggested fix
okButton={{ label: t("Cancel subscription"), onClick: async () => { - const productId = cancelProductId; - if (!productId) return; + const productId = cancelProductId ?? throwErr("cancelProductId should not be null when okButton is clicked"); if (props.customerType === "team") { await stackApp.cancelSubscription({ teamId: props.customer.id, productId }); } else { await stackApp.cancelSubscription({ productId }); } setCancelProductId(null); }, }}
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
packages/react/package.jsonpackages/stack-shared/src/interface/server-interface.tspackages/stack/package.jsonpackages/template/package-template.jsonpackages/template/package.jsonpackages/template/src/components-page/account-settings/payments/payments-panel.tsxpackages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/stack/package.json
- packages/stack-shared/src/interface/server-interface.ts
- packages/template/package.json
- packages/react/package.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never usetoast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. UserunAsynchronouslyorrunAsynchronouslyWithAlertinstead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g.,transition-colors hover:transition-none)
Don't useDate.now()for measuring elapsed (real) time; instead useperformance.now()
Use ES6 maps instead of records wherever possible
Files:
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., useusePathnameinstead ofawait params)
Code defensively using?? throwErr(...)instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid theanytype. When usingany, leave a comment explaining why and how the type system fails or how errors would still be caught
Files:
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsx
{.env*,**/*.{ts,tsx,js,jsx}}
📄 CodeRabbit inference engine (AGENTS.md)
All environment variables should be prefixed with
STACK_(orNEXT_PUBLIC_STACK_if public) to ensure Turborepo picks up changes and improve readability
Files:
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧠 Learnings (5)
📚 Learning: 2026-01-11T06:44:57.542Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 1069
File: packages/template/src/components-page/onboarding.tsx:59-61
Timestamp: 2026-01-11T06:44:57.542Z
Learning: In the stack-auth codebase, Button components from stackframe/stack-ui (including those used via MessageCard props like primaryAction and secondaryAction) can accept async functions directly and automatically handle loading states with indicators. No need to wrap these with runAsynchronouslyWithAlert.
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : For blocking alerts and errors, never use `toast`, as they are easily missed by the user. Instead, use alerts
Applied to files:
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧬 Code graph analysis (1)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (5)
packages/template/src/lib/stack-app/customers/index.ts (2)
CustomerBilling(72-75)CustomerPaymentMethodSetupIntent(77-80)packages/template/src/lib/translations.tsx (1)
useTranslation(4-19)packages/template/src/components-page/account-settings/section.tsx (1)
Section(3-22)packages/stack-shared/src/utils/promises.tsx (1)
runAsynchronouslyWithAlert(312-328)apps/e2e/tests/js/convex.test.ts (1)
open(31-33)
🪛 Biome (2.1.2)
packages/template/package-template.json
[error] 90-90: The key // was already declared.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Vercel Agent Review
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: lint_and_build (latest)
- GitHub Check: setup-tests
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: setup-tests-with-custom-base-port
🔇 Additional comments (12)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (6)
45-45: LGTM!The new billing-related type imports align with the new methods introduced in
_createCustomer.
1171-1176: LGTM!The subscription field mapping correctly handles the optional
current_period_endwith a null check before creating the Date object.
1181-1195: LGTM!The mapping is straightforward and consistent with other response transformation helpers in this file.
1632-1653: LGTM!The new billing methods follow the established patterns in this file:
getBilling/useBillingpair matches other async getter/React hook pairscreatePaymentMethodSetupIntentandsetDefaultPaymentMethodFromSetupIntentcorrectly interact with the interface layer- Cache refresh after mutation in
setDefaultPaymentMethodFromSetupIntentensures data consistency
1670-1672: LGTM!Consistent update to use
effectiveSessionfor billing operations.
1629-1629: No action needed. The pattern is correct and intentional. All billing endpoints (getCustomerBilling,createCustomerPaymentMethodSetupIntent,setDefaultCustomerPaymentMethodFromSetupIntent,createCheckoutUrl) explicitly acceptsession: InternalSession | null, confirming they are designed to support both authenticated and unauthenticated requests. Creating a session withrefreshToken: nullis the standard fallback pattern used throughout the codebase (e.g., insendClientRequest) for operations that don't require authentication.packages/template/src/components-page/account-settings/payments/payments-panel.tsx (6)
1-11: LGTM!Imports are well-organized and appropriate. Good use of
runAsynchronouslyWithAlertandthrowErras per coding guidelines.
81-108: LGTM!Good use of async onClick handler - the Button component from
@stackframe/stack-uiautomatically handles loading states. The defensive null checks for stripe, elements, and card are appropriate. Based on learnings, this pattern is correct.
113-126: LGTM!Clean conditional rendering logic with appropriate fallbacks for mock mode and missing customer.
128-175: LGTM!Mock panel provides appropriate static data for preview mode with properly disabled interactive elements.
260-297: LGTM!Good implementation of the active plans list with:
- Proper filtering by customer type
- Well-formatted quantity suffix and renewal dates using
Intl.DateTimeFormat- Appropriate conditional rendering for the cancel button based on
isCancelable- Reasonable fallback key using index when
product.idis null
299-322: LGTM!The cancel subscription dialog is well-implemented with proper confirmation flow using
ActionDialog. The asynconClickhandler is correctly handled by the ActionDialog component per the codebase patterns.
<!
--
Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.