Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Jan 7, 2026

https://www.loom.com/share/f218c4d16b4d4306be93685aad7aed7a

Summary by CodeRabbit

  • New Features

    • One-click subscription switching (client + server) with a UI dialog to choose alternative plans
    • Products now show available plan alternatives (switch options)
  • Improvements

    • More robust default payment-method handling, validation, and clearer error when none is set
    • Improved payment-method retrieval, customer metadata persistence, and safer mock/test behavior
  • Tests

    • End-to-end tests for plan switching and updated billing snapshots

✏️ Tip: You can customize this high-level summary in your review settings.

@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@vercel
Copy link

vercel bot commented Jan 7, 2026

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

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 14, 2026 0:10am
stack-dashboard Ready Ready Preview, Comment Jan 14, 2026 0:10am
stack-demo Ready Ready Preview, Comment Jan 14, 2026 0:10am
stack-docs Ready Ready Preview, Comment Jan 14, 2026 0:10am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Note

Other AI code review bot(s) detected

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

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Payment-method set-default
apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
Retrieve and validate setup intent early, ensure it belongs to the customer, allow mock-key bypasses, handle missing payment_method (create+attach test card in mock mode), persist Stripe customer metadata and update invoice_settings, return default payment method summary.
Products listing (switch options)
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
Compute switchOptionsByCatalogId from tenancy products with eligibility filters; include switch_options in subscription-type response items (per catalog, excluding current product).
Products switch (NEW)
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
New POST route: validate auth, products, catalog/type/price rules and purchases; resolve Stripe customer and default payment method; create/update Stripe subscription; upsert Prisma subscription record; return success or descriptive errors.
Stripe utilities
apps/backend/src/lib/payments.tsx
Add env-driven Stripe mock path and mock lookup shortcut; change default-payment lookup to list payment methods (type: card, limit:1) and adapt card mapping/null-safety.
Client API surface
packages/stack-shared/src/interface/client-interface.ts
Add switchSubscription(...) method calling POST /payments/products/{customer_type}/{customer_id}/switch.
Schemas & Known errors
packages/stack-shared/src/interface/crud/products.ts, packages/stack-shared/src/known-errors.tsx
Add customerProductSwitchOptionSchema and optional switch_options in product read schema; add KnownError DEFAULT_PAYMENT_METHOD_REQUIRED (400).
Template UI & client impl
packages/template/src/components-page/account-settings/payments/payments-panel.tsx, packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts, packages/template/src/lib/stack-app/customers/index.ts
Add switch UI/dialog, selection state, error handling for DefaultPaymentMethodRequired, expose switchOptions on CustomerProduct, add switchSubscription on Customer and client impl, refresh/invalidate caches after switching.
Tests / Snapshots
apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts, apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
Update billing snapshots: default_payment_method now populated; add e2e tests for switch endpoint rejecting cross-catalog switches and include-by-default targets.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble configs, hop through Stripe's gate,
I stitch plans together, swap them nice and straight,
From product to product, catalogs align,
A carrot for testers, a small rabbit sign —
Hop on, click swap, subscriptions feel fine.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides only a Loom video link without any textual explanation, missing required context about what changes were made, why, or how to test them. Replace the video link with a detailed written description covering the changes made, rationale, testing approach, and any relevant implementation details or breaking changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'upgrade/downgrade plans' clearly and concisely summarizes the main feature being introduced: enabling users to switch between subscription plans.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (2)

243-248: Consider explicit price selection for multi-price products.

selectedPriceId takes the first key from Object.keys(selectedSwitchOption.prices). If products can have multiple prices (e.g., monthly/yearly), users might want to choose. Currently, this automatically selects the first one without user input.

If this is intentional for MVP, a follow-up could add price selection when Object.keys(prices).length > 1.


325-334: Consider replacing non-null assertion with defensive coding.

Line 330 uses product.id! despite the coding guidelines preferring ?? throwErr(...) for defensive coding. While canSwitchPlans already checks !!product.id, the non-null assertion could be replaced for consistency.

Proposed fix
                   {canSwitchPlans && (
                     <Button
                       variant="secondary"
                       color="neutral"
-                      onClick={() => openSwitchDialog(product.id!, product.switchOptions?.[0]?.productId ?? null)}
+                      onClick={() => openSwitchDialog(product.id ?? throwErr("product.id should be defined when canSwitchPlans is true"), product.switchOptions?.[0]?.productId ?? null)}
                     >
                       {t("Change plan")}
                     </Button>
                   )}

Note: You'll need to import throwErr or use an inline throw, e.g.:

product.id ?? (() => { throw new Error("product.id should be defined"); })()
📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da7e00d and 7e088f3.

📒 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 use toast, 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. Use runAsynchronously or runAsynchronouslyWithAlert instead 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 use Date.now() for measuring elapsed (real) time; instead use performance.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., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, 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_ (or NEXT_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 (4)
📚 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} : 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
📚 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-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
🧬 Code graph analysis (1)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (4)
packages/stack-shared/src/known-errors.tsx (2)
  • KnownErrors (1689-1691)
  • KnownErrors (1693-1823)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
packages/template/src/components-page/account-settings/section.tsx (1)
  • Section (3-22)
apps/e2e/tests/js/convex.test.ts (1)
  • open (31-33)
⏰ 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: check_prisma_migrations (22.x)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • 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: LGTM!

Good use of runAsynchronously with a custom onError handler for structured error handling.


384-403: LGTM!

Good error handling using Result.fromThrowingAsync with proper "prevent-close" return on failure. This addresses the previous feedback about dialogs closing unexpectedly on errors.


411-428: LGTM!

The Select component properly manages plan selection state and the mapping of switchOptions to SelectItem components is clean.


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

❤️ Share

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 7, 2026

Greptile Summary

Added plan switching functionality allowing users and teams to upgrade/downgrade subscriptions within the same product catalog.

Key Changes:

  • New /switch endpoint handles subscription product changes with validation for catalog membership, payment methods, and product eligibility
  • Frontend UI in PaymentsPanel displays available switch options with dialog for plan selection
  • Switch options automatically filtered to show only interval-priced products in the same catalog (excluding add-ons and include-by-default products)
  • Cache invalidation properly implemented in client app after switching
  • New DefaultPaymentMethodRequired known error for payment method validation
  • Comprehensive test coverage for authorization, catalog restrictions, and error cases

Critical Issues:

  • Lines 243-244 in switch route use existingItem.current_period_start/end instead of updatedSubscription.current_period_start/end, causing incorrect period tracking when Stripe modifies billing periods during plan switches
  • Lines 208 and 253 use empty conditional spreads that may not set default_payment_method when resolvedPaymentMethodId is defined

Confidence Score: 3/5

  • This PR has critical bugs in billing period tracking that will cause data inconsistencies
  • Score reflects critical logic errors on lines 243-244 that use wrong field for period dates (will cause incorrect billing periods to be stored in database), plus two potential issues with conditional payment method setting that may fail silently
  • Pay close attention to apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts - contains critical bugs in subscription period tracking

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts New switch endpoint with thorough validation but missing cache invalidation after subscription updates
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts Added switch_options field to GET response, properly filtered by catalog and interval prices
packages/template/src/components-page/account-settings/payments/payments-panel.tsx Added plan switching UI with dialog and select dropdown
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Added switchSubscription method with proper cache invalidation

Sequence Diagram

sequenceDiagram
    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
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a 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

Edit Code Review Agent Settings | Greptile

@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@cmux-agent
Copy link

cmux-agent bot commented Jan 10, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@cmux-agent
Copy link

cmux-agent bot commented Jan 12, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Jan 13, 2026
@cmux-agent
Copy link

cmux-agent bot commented Jan 13, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

Base automatically changed from payments-shown-subscriptions-in-settings to dev January 13, 2026 21:49
@cmux-agent
Copy link

cmux-agent bot commented Jan 13, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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: effectiveStripeCustomer appears redundant.

effectiveStripeCustomer is assigned stripeCustomer and never reassigned. Consider removing this intermediate variable and using stripeCustomer directly on lines 101 and 107.

🔧 Suggested simplification
-    const expectedCustomerType = typedToUppercase(params.customer_type);
-    const effectiveStripeCustomer = stripeCustomer;
+    const expectedCustomerType = typedToUppercase(params.customer_type);

Then replace effectiveStripeCustomer with stripeCustomer on lines 101 and 107.


91-105: Combine the two stripe.customers.update calls into one.

Two separate API calls update the same customer. This can be consolidated to reduce latency and API usage. Also, defaultPaymentMethodId and default_payment_method_id in 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 toast as they are easily missed by the user. The DefaultPaymentMethodRequired error 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 setting proration_behavior when updating subscription items.

The item replacement using items: [{ id: existingItem.id, ... }] is the correct Stripe API approach. However, Stripe defaults proration_behavior to "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 set proration_behavior in 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.

Team is 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:

  1. Successful switch between paid plans in the same catalog
  2. Switch with different pricing intervals
  3. Team-based subscription switching (given Team was 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 of any in test setup parameters.

The products and catalogs parameters use Record<string, any>. While proper types exist (inlineProductSchema for products and a catalog type in the config schema), the test helper needs flexibility to accept various test data structures. Add a comment explaining that any is used here because test data is provided in camelCase format (e.g., displayName, customerType) before being passed to Project.updateConfig, which handles schema transformation downstream.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 570249f and be4f664.

📒 Files selected for processing (12)
  • apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/lib/payments.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • packages/stack-shared/src/interface/client-interface.ts
  • packages/stack-shared/src/interface/crud/products.ts
  • packages/stack-shared/src/known-errors.tsx
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/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 .toMatchInlineSnapshot over 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.ts
  • apps/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 use toast, 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. Use runAsynchronously or runAsynchronouslyWithAlert instead 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 use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • packages/template/src/lib/stack-app/customers/index.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • packages/stack-shared/src/interface/client-interface.ts
  • apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • apps/backend/src/lib/payments.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/stack-shared/src/interface/crud/products.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts
  • packages/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., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, 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.ts
  • packages/template/src/lib/stack-app/customers/index.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • packages/stack-shared/src/interface/client-interface.ts
  • apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • apps/backend/src/lib/payments.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/stack-shared/src/interface/crud/products.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts
  • packages/stack-shared/src/known-errors.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_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.ts
  • packages/template/src/lib/stack-app/customers/index.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • packages/stack-shared/src/interface/client-interface.ts
  • apps/backend/src/app/api/latest/payments/payment-method/[customer_type]/[customer_id]/set-default/route.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • apps/backend/src/lib/payments.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/stack-shared/src/interface/crud/products.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/billing.test.ts
  • packages/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.ts
  • apps/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 customerProductSwitchOptionSchema correctly pairs product IDs with their inline product representations, and the optional switch_options array on customerProductReadSchema appropriately 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_KEY follows 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_mockstripekey is 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_mockstripekey is a sentinel value for enabling mock behavior, not an actual secret. The test card creation (using Stripe's well-known test card 4242424242424242) is standard practice for mock environments.

packages/stack-shared/src/known-errors.tsx (1)

1675-1687: LGTM! New DefaultPaymentMethodRequired error 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! The switchSubscription method follows established patterns.

The implementation is consistent with cancelSubscription. Note that customer_type is restricted to "user" | "team" (excluding "custom") unlike cancelSubscription which 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 switchOptions mapping correctly transforms the API response into the client-facing format.


1678-1693: LGTM!

The switchSubscription implementation 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 cancelSubscription method.

packages/template/src/lib/stack-app/customers/index.ts (2)

48-52: LGTM!

The switchOptions type 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 switchSubscription method signature correctly defines the parameters for switching subscriptions, with required fromProductId and toProductId, and optional priceId and quantity.

apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (2)

49-70: LGTM!

The switchOptionsByCatalogId construction 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_options for 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_method object both initially and after setup-intent creation, which aligns with the backend changes described in the summary.

@cmux-agent
Copy link

cmux-agent bot commented Jan 13, 2026

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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. While product.id is checked in canSwitchPlans, 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

📥 Commits

Reviewing files that changed from the base of the PR and between be4f664 and da7e00d.

📒 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 use toast, 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. Use runAsynchronously or runAsynchronouslyWithAlert instead 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 use Date.now() for measuring elapsed (real) time; instead use performance.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., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, 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_ (or NEXT_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 of runAsynchronously with error handling.

The async operation is properly wrapped with runAsynchronously and includes error handling via handleAsyncError. This aligns with the codebase patterns.


243-248: Derived switch state logic looks correct.

The computed values for switchSourceProduct, switchOptions, selectedSwitchOption, and selectedPriceId properly 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.

@BilalG1 BilalG1 enabled auto-merge (squash) January 14, 2026 00:04
@BilalG1 BilalG1 merged commit d6dc85b into dev Jan 14, 2026
27 checks passed
@BilalG1 BilalG1 deleted the payments-upgrade-downgrade-ui branch January 14, 2026 00:17
@promptless
Copy link
Contributor

promptless bot commented Jan 21, 2026

📝 Documentation updates detected!

New suggestion: Document subscription plan switching feature

@coderabbitai coderabbitai bot mentioned this pull request Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants