-
Notifications
You must be signed in to change notification settings - Fork 498
upgrade/downgrade plans #1087
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
upgrade/downgrade plans #1087
Conversation
…to payments-shown-subscriptions-in-settings
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds subscription-switching end-to-end: backend switch POST route, enriches products listing with switch options, updates Stripe payment-method handling and customer metadata, client/template API and UI to initiate switches, schema and KnownError additions, and e2e tests plus snapshot updates. Changes
Sequence DiagramsequenceDiagram
participant Client as Client / UI
participant Backend as Backend Switch Endpoint
participant Stripe as Stripe API
participant DB as Database (Prisma)
Client->>Backend: POST /payments/.../switch (from_product_id, to_product_id, price_id?, quantity?)
activate Backend
Backend->>Backend: Validate auth, params, product configs, catalog match
Backend->>DB: Load purchases & subscriptions for customer/catalog
Backend->>Stripe: Resolve Stripe customer & default payment method
alt No default payment method
Backend-->>Client: 400 DEFAULT_PAYMENT_METHOD_REQUIRED
else Has payment method
Backend->>Stripe: Create or update Stripe subscription (product/price/quantity)
Stripe-->>Backend: Subscription details (id, status, timestamps)
Backend->>DB: Upsert Prisma subscription record with Stripe details
DB-->>Backend: Confirm
Backend-->>Client: 200 { success: true }
end
deactivate Backend
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
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
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (3)**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
{.env*,**/*.{ts,tsx,js,jsx}}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (4)📚 Learning: 2025-10-11T04:13:19.308ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-11T06:44:57.542ZApplied to files:
🧬 Code graph analysis (1)packages/template/src/components-page/account-settings/payments/payments-panel.tsx (4)
⏰ 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)
🔇 Additional comments (3)
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 SummaryAdded plan switching functionality allowing users and teams to upgrade/downgrade subscriptions within the same product catalog. Key Changes:
Critical Issues:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Frontend as PaymentsPanel
participant ClientApp as StackClientApp
participant API as Switch Route
participant Stripe
participant DB as Database
User->>Frontend: Click "Change plan"
Frontend->>Frontend: Open switch dialog
User->>Frontend: Select new plan
User->>Frontend: Click "Switch plan"
Frontend->>ClientApp: switchSubscription({fromProductId, toProductId, priceId})
ClientApp->>API: POST /payments/products/{type}/{id}/switch
API->>API: Validate client permissions
API->>API: Validate products in same catalog
API->>API: Validate target product is switchable
API->>DB: Check existing subscription
API->>DB: Check one-time purchases in catalog
API->>API: Check payment method required
alt Has Stripe Subscription
API->>Stripe: Retrieve existing subscription
API->>Stripe: Create new product
API->>Stripe: Update subscription with new price
Stripe-->>API: Updated subscription
API->>DB: Update subscription record
else Include-by-default to Paid Plan
API->>Stripe: Get customer with payment method
API->>Stripe: Create new product
API->>Stripe: Create new subscription
Stripe-->>API: Created subscription
API->>DB: Create subscription record
end
API-->>ClientApp: {success: true}
ClientApp->>ClientApp: Invalidate cache
ClientApp-->>Frontend: Success
Frontend->>Frontend: Close dialog
Frontend->>Frontend: Refresh products list
|
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.
12 files reviewed, 4 comments
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Show resolved
Hide resolved
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Show resolved
Hide resolved
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Outdated
Show resolved
Hide resolved
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Show resolved
Hide resolved
...rc/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
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 |
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md -->
…-subscriptions-in-settings
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 |
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 |
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 |
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: 6
🤖 Fix all issues with AI agents
In
@apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts:
- Around line 201-218: The DB update is pulling period timestamps from the
pre-update existingItem, which can be stale; change the code in
prisma.subscription.update to read current_period_start/current_period_end from
the updated Stripe subscription (updatedSubscription) instead of existingItem —
prefer updatedSubscription.items.data[0].current_period_start and
.current_period_end if present, or fall back to
updatedSubscription.current_period_start/.current_period_end, and convert them
to Dates (multiply by 1000) before storing.
- Line 168: The code currently calls stripe.products.create (e.g., the call
assigning stripeProduct from toProduct.displayName) on every subscription switch
which creates orphaned Stripe products; instead, look up or reuse an existing
product first (use stripe.products.list or stripe.products.retrieve by a saved
product_id or a unique metadata key based on toProduct/plan), only call
stripe.products.create when no matching product exists, and persist the returned
product.id back to your customer/plan record so future switches call
stripe.products.retrieve; also add optional archival/cleanup logic for legacy
orphaned products.
In @apps/backend/src/lib/payments.tsx:
- Around line 564-577: The function currently returns the first card from
customers.listPaymentMethods which can differ from the customer's configured
default; change it to first read
options.stripeCustomer.invoice_settings?.default_payment_method and if present
call options.stripe.paymentMethods.retrieve(defaultId) and map that
PaymentMethod to the returned summary (id, brand, last4, exp_month, exp_year);
only if no default_payment_method exists fall back to
options.stripe.customers.listPaymentMethods(options.stripeCustomer.id, { type:
"card", limit: 1 }) and map the first entry as before so callers (e.g.,
switch/route.ts expecting a default) get the actual default when configured.
In @apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts:
- Around line 37-43: The test fixture in the snapshot uses a hardcoded expired
card expiration (default_payment_method.exp_year: 2025, exp_month: 8); update
the mock in apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts so
default_payment_method.exp_year and exp_month represent a future date (e.g.,
exp_year: 2030, exp_month: 12) to avoid expiration validation failures in tests
and update any snapshot/assertion that expects the old values.
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Around line 324-354: The component references an undefined variable
canSwitchPlans and renders the "Cancel subscription" button twice: remove the
duplicate button (the one outside the flex column) and either define or replace
canSwitchPlans with the correct prop/state/derived boolean (e.g., use an
existing variable like product.switchOptions?.length > 0 or a prop passed into
the component) so that the conditional Button using
openSwitchDialog(product.id!, product.switchOptions?.[0]?.productId ?? null)
only renders when switching is actually allowed; keep the cancel flow using
setCancelProductId(product.id) once inside the flex column.
- Around line 391-408: The okButton.onClick handler calls
props.customer.switchSubscription asynchronously without error handling; wrap
the call in a try/catch inside okButton.onClick (checking switchFromProductId,
switchToProductId, selectedPriceId as already done), await
props.customer.switchSubscription(...) in the try block and call
closeSwitchDialog() on success, and in the catch block capture the error and
surface user feedback (e.g., set an error state, call a toast/notification
helper, or use ActionDialog error prop) so the dialog stays open on failure and
the user sees the error; reference okButton.onClick,
props.customer.switchSubscription, closeSwitchDialog, selectedPriceId,
switchFromProductId, and switchToProductId.
🧹 Nitpick comments (7)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (2)
68-69: Minor:effectiveStripeCustomerappears redundant.
effectiveStripeCustomeris assignedstripeCustomerand never reassigned. Consider removing this intermediate variable and usingstripeCustomerdirectly on lines 101 and 107.🔧 Suggested simplification
- const expectedCustomerType = typedToUppercase(params.customer_type); - const effectiveStripeCustomer = stripeCustomer; + const expectedCustomerType = typedToUppercase(params.customer_type);Then replace
effectiveStripeCustomerwithstripeCustomeron lines 101 and 107.
91-105: Combine the twostripe.customers.updatecalls into one.Two separate API calls update the same customer. This can be consolidated to reduce latency and API usage. Also,
defaultPaymentMethodIdanddefault_payment_method_idin metadata store the same value—consider keeping only one.🔧 Proposed consolidation
- await stripe.customers.update(stripeCustomer.id, { - metadata: { - customerId: params.customer_id, - customerType: expectedCustomerType, - defaultPaymentMethodId: paymentMethodId, - default_payment_method_id: paymentMethodId, - hasPaymentMethod: "true", - }, - }); - - await stripe.customers.update(effectiveStripeCustomer.id, { - invoice_settings: { - default_payment_method: paymentMethodId, - }, - }); + await stripe.customers.update(stripeCustomer.id, { + metadata: { + customerId: params.customer_id, + customerType: expectedCustomerType, + default_payment_method_id: paymentMethodId, + hasPaymentMethod: "true", + }, + invoice_settings: { + default_payment_method: paymentMethodId, + }, + });packages/template/src/components-page/account-settings/payments/payments-panel.tsx (1)
206-216: Consider using an alert instead of toast for the payment method error.Per coding guidelines, blocking alerts and errors should not use
toastas they are easily missed by the user. TheDefaultPaymentMethodRequirederror requires user action (adding a payment method), so an alert might be more appropriate to ensure the user notices it.♻️ Suggested change
const handleAsyncError = (error: unknown) => { if (error instanceof KnownErrors.DefaultPaymentMethodRequired) { - toast({ - title: t("No default payment method"), - description: t("Add a payment method before switching plans."), - variant: "destructive", - }); + alert(t("No default payment method. Add a payment method before switching plans.")); return; } alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === "development" ? "check the browser console for the full error." : "report this to the developer."}\n\n${error}`); };apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts (1)
170-198: Consider explicitly settingproration_behaviorwhen updating subscription items.The item replacement using
items: [{ id: existingItem.id, ... }]is the correct Stripe API approach. However, Stripe defaultsproration_behaviorto"create_prorations"when updating subscription items. If the business logic requires different proration handling (e.g.,"none"to avoid creating proration invoices, or"always_invoice"to bill prorations immediately), explicitly setproration_behaviorin the update parameters rather than relying on the default.apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts (3)
2-2: Remove unused import.
Teamis imported but never used in this file.🧹 Suggested fix
-import { Auth, Payments, Project, Team, niceBackendFetch } from "../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../backend-helpers";
16-106: Consider adding happy path test coverage.The tests cover two important error scenarios, but there's no test for a successful plan switch. Based on the coding guidelines: "Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry."
Consider adding tests for:
- Successful switch between paid plans in the same catalog
- Switch with different pricing intervals
- Team-based subscription switching (given
Teamwas imported, possibly intended)Would you like me to draft additional test cases for the happy path scenarios?
4-13: Add a comment explaining the use ofanyin test setup parameters.The
productsandcatalogsparameters useRecord<string, any>. While proper types exist (inlineProductSchemafor products and a catalog type in the config schema), the test helper needs flexibility to accept various test data structures. Add a comment explaining thatanyis used here because test data is provided in camelCase format (e.g.,displayName,customerType) before being passed toProject.updateConfig, which handles schema transformation downstream.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.tsapps/backend/src/lib/payments.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.tspackages/stack-shared/src/interface/client-interface.tspackages/stack-shared/src/interface/crud/products.tspackages/stack-shared/src/known-errors.tsxpackages/template/src/components-page/account-settings/payments/payments-panel.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 (4)
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.test.{ts,tsx,js,jsx}: Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Use.toMatchInlineSnapshotover other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted
Files:
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts
**/*.{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:
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.tspackages/template/src/lib/stack-app/customers/index.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.tspackages/stack-shared/src/interface/client-interface.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.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxpackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/stack-shared/src/interface/crud/products.tsapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tspackages/stack-shared/src/known-errors.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:
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.tspackages/template/src/lib/stack-app/customers/index.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.tspackages/stack-shared/src/interface/client-interface.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.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxpackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/stack-shared/src/interface/crud/products.tsapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tspackages/stack-shared/src/known-errors.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:
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.tspackages/template/src/lib/stack-app/customers/index.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.tspackages/stack-shared/src/interface/client-interface.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.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/lib/payments.tsxpackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tspackages/stack-shared/src/interface/crud/products.tsapps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.tspackages/stack-shared/src/known-errors.tsx
🧠 Learnings (6)
📚 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 **/*.test.{ts,tsx,js,jsx} : Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Applied to files:
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
📚 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 {.env*,**/*.{ts,tsx,js,jsx}} : All environment variables should be prefixed with `STACK_` (or `NEXT_PUBLIC_STACK_` if public) to ensure Turborepo picks up changes and improve readability
Applied to files:
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.tsapps/backend/src/lib/payments.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-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: 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 packages/stack-shared/src/config/schema.ts : 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
🧬 Code graph analysis (5)
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts (2)
apps/e2e/tests/helpers.ts (1)
it(12-12)apps/e2e/tests/backend/backend-helpers.ts (1)
niceBackendFetch(109-173)
packages/stack-shared/src/interface/client-interface.ts (2)
packages/stack-shared/src/sessions.ts (1)
InternalSession(81-311)packages/stack-shared/src/utils/urls.tsx (1)
urlString(314-316)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (4)
apps/backend/src/lib/payments.tsx (1)
ensureStripeCustomerForCustomer(532-550)packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)packages/stack-shared/src/utils/strings.tsx (1)
typedToUppercase(30-33)packages/stack-shared/src/utils/env.tsx (1)
getEnvVariable(16-58)
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (1)
apps/backend/src/lib/payments.tsx (1)
productToInlineProduct(580-596)
apps/backend/src/lib/payments.tsx (1)
packages/stack-shared/src/utils/env.tsx (2)
getEnvVariable(16-58)getNodeEnvironment(76-78)
🪛 Gitleaks (8.30.0)
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
[high] 71-71: Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data.
(stripe-access-token)
[high] 76-76: Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data.
(stripe-access-token)
apps/backend/src/lib/payments.tsx
[high] 22-22: Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data.
(stripe-access-token)
⏰ 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: lint_and_build (latest)
- GitHub Check: restart-dev-and-test
- GitHub Check: docker
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: all-good
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests-with-custom-base-port
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: setup-tests
🔇 Additional comments (13)
packages/stack-shared/src/interface/crud/products.ts (1)
4-7: LGTM! Schema additions for switch options are well-structured.The
customerProductSwitchOptionSchemacorrectly pairs product IDs with their inline product representations, and the optionalswitch_optionsarray oncustomerProductReadSchemaappropriately supports the new plan switching feature.Also applies to: 19-19
apps/backend/src/lib/payments.tsx (2)
21-22: Environment-based mock configuration looks correct.The
STACK_STRIPE_SECRET_KEYfollows the naming convention, and the mock mode is appropriately restricted to development/test environments. The static analysis warning about a "Stripe Access Token" on line 22 is a false positive—sk_test_mockstripekeyis a placeholder sentinel value for enabling mock behavior, not an actual secret key.
511-514: Mock mode fallback for Stripe customer lookup is reasonable.When using the mock Stripe in development/test, bypassing exact metadata matching and using the first available customer is a practical workaround for mock limitations.
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts (1)
70-90: Mock mode handling is appropriate for test scenarios.The static analysis warnings about "Stripe Access Token" on lines 71 and 76 are false positives—
sk_test_mockstripekeyis a sentinel value for enabling mock behavior, not an actual secret. The test card creation (using Stripe's well-known test card4242424242424242) is standard practice for mock environments.packages/stack-shared/src/known-errors.tsx (1)
1675-1687: LGTM! NewDefaultPaymentMethodRequirederror follows established patterns.The error definition is well-structured with appropriate status code (400), clear messaging, and proper JSON serialization/deserialization. This integrates well with the subscription switching feature.
Also applies to: 1820-1820
packages/stack-shared/src/interface/client-interface.ts (1)
1826-1853: LGTM! TheswitchSubscriptionmethod follows established patterns.The implementation is consistent with
cancelSubscription. Note thatcustomer_typeis restricted to"user" | "team"(excluding"custom") unlikecancelSubscriptionwhich accepts all three types—this restriction aligns with the API design where switch operations are only available for direct user and team subscriptions.packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (2)
1177-1181: LGTM!The
switchOptionsmapping correctly transforms the API response into the client-facing format.
1678-1693: LGTM!The
switchSubscriptionimplementation correctly:
- Calls the interface method with properly mapped parameters
- Refreshes the billing cache
- Invalidates the appropriate products cache based on customer type
The pattern is consistent with the existing
cancelSubscriptionmethod.packages/template/src/lib/stack-app/customers/index.ts (2)
48-52: LGTM!The
switchOptionstype definition correctly models the switch options with product ID, display name, and prices. The optional nature (?) appropriately reflects that not all products have switch options.
99-100: LGTM!The
switchSubscriptionmethod signature correctly defines the parameters for switching subscriptions, with requiredfromProductIdandtoProductId, and optionalpriceIdandquantity.apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (2)
49-70: LGTM!The
switchOptionsByCatalogIdconstruction correctly:
- Filters products by customer type and visibility
- Excludes include-by-default and add-on products
- Only includes products with interval pricing
- Groups eligible products by catalog ID
This aligns with the validation logic in the switch route.
75-97: LGTM!The enhanced response mapping correctly:
- Only includes
switch_optionsfor subscription-type products with a catalog ID- Filters out the current product from its own switch options
- Preserves all existing fields in the response
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts (1)
24-79: Test coverage looks good for billing status retrieval.The test properly validates that the API returns a populated
default_payment_methodobject both initially and after setup-intent creation, which aligns with the backend changes described in the summary.
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Show resolved
Hide resolved
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
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: 2
🤖 Fix all issues with AI agents
In
@packages/template/src/components-page/account-settings/payments/payments-panel.tsx:
- Around line 206-216: The handler handleAsyncError currently uses toast for the
blocking KnownErrors.DefaultPaymentMethodRequired case; replace the toast
invocation with a blocking alert so the user sees the error immediately. Locate
handleAsyncError and where it checks error instanceof
KnownErrors.DefaultPaymentMethodRequired, remove the toast(...) call and instead
call alert(...) with a clear combined message using the same translated strings
t("No default payment method") and t("Add a payment method before switching
plans.") (no "variant" needed) so the alert displays both title and description.
- Around line 382-399: Wrap the async logic inside okButton.onClick (which calls
switchSubscription) with explicit error handling by using the same pattern as
openPaymentDialog: call switchSubscription inside a try/catch (or use
runAsynchronously with onError: handleAsyncError) so errors are passed to
handleAsyncError and user-friendly messages (e.g., DefaultPaymentMethodRequired)
are shown; ensure you still call closeSwitchDialog() only on success and keep
the disabled prop check for switchFromProductId, switchToProductId, and
selectedPriceId.
🧹 Nitpick comments (2)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (2)
326-334: Replace non-null assertion with defensive coding.Per coding guidelines, use
?? throwErr(...)instead of non-null assertions. Whileproduct.idis checked incanSwitchPlans, defensive coding provides better error messages if assumptions are violated.Proposed fix
{canSwitchPlans && ( <Button variant="secondary" color="neutral" - onClick={() => openSwitchDialog(product.id!, product.switchOptions?.[0]?.productId ?? null)} + onClick={() => openSwitchDialog(product.id ?? throwErr("product.id is null despite canSwitchPlans being true"), product.switchOptions?.[0]?.productId ?? null)} > {t("Change plan")} </Button> )}
429-431: Minor: Inconsistent formatting in closing tags.Suggested formatting cleanup
</Section> - ) - } - </div > + )} + </div> );
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx
🧰 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/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/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/components-page/account-settings/payments/payments-panel.tsx
🧠 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-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: 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
⏰ 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: restart-dev-and-test-with-custom-base-port
- GitHub Check: lint_and_build (latest)
- GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
- GitHub Check: all-good
- GitHub Check: restart-dev-and-test
- GitHub Check: build (22.x)
- GitHub Check: setup-tests-with-custom-base-port
🔇 Additional comments (3)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (3)
218-225: Good use ofrunAsynchronouslywith error handling.The async operation is properly wrapped with
runAsynchronouslyand includes error handling viahandleAsyncError. This aligns with the codebase patterns.
243-248: Derived switch state logic looks correct.The computed values for
switchSourceProduct,switchOptions,selectedSwitchOption, andselectedPriceIdproperly derive from state and handle null/undefined cases appropriately.
401-427: Switch dialog UI implementation is well-structured.The dialog properly handles the empty state (no switch options), uses accessible Select components, and displays plan names clearly. The conditional rendering between empty state and plan selector is clean.
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
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
Show resolved
Hide resolved
|
📝 Documentation updates detected! New suggestion: Document subscription plan switching feature |
https://www.loom.com/share/f218c4d16b4d4306be93685aad7aed7a
Summary by CodeRabbit
New Features
Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.