Skip to content

Conversation

@panteliselef
Copy link
Member

@panteliselef panteliselef commented Oct 29, 2025

Description

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Chores
    • Updated supported backend and frontend API versions to 2025-10-01 and added a changeset for patch releases.
  • Bug Fixes / UX
    • Pricing and subscription UIs now handle plans without annual pricing more robustly (fee displays, period toggles, switch eligibility).
    • Billing totals and due-amount displays are now guarded to avoid showing incorrect or missing totals.
  • Tests
    • Updated tests to reflect the new API version and absent annual-fee scenarios.

@changeset-bot
Copy link

changeset-bot bot commented Oct 29, 2025

🦋 Changeset detected

Latest commit: ed3a125

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@clerk/clerk-js Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/remix Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Oct 29, 2025

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

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Oct 29, 2025 9:26pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

Bumped frontend/backend supported API versions; made BillingPlan annual fee fields nullable across types and JSON; updated model deserialization (adds a debug log); updated many UI components and tests to handle optional annual pricing; renamed a checkout param from paymentSourceId to paymentMethodId; added small billing-utils guards and a changeset.

Changes

Cohort / File(s) Summary
API Version Constants
packages/backend/src/constants.ts, packages/clerk-js/src/core/constants.ts
Bumped supported API versions from '2025-04-10' to '2025-10-01'.
Changeset
.changeset/hot-jars-smell.md
Adds/updates changeset recording patch releases and the API version bump.
Types / JSON shapes
packages/types/src/billing.ts, packages/types/src/json.ts
BillingPlan fields annualFee and annualMonthlyFee and corresponding JSON fields changed from non-null to nullable (`...
BillingPlan model
packages/clerk-js/src/core/resources/BillingPlan.ts
annualFee and annualMonthlyFee are now nullable; fromJSON maps nulls and includes a console.log debug statement.
UI: pricing / plans / subscriptions
packages/clerk-js/src/ui/components/...
PricingTable/*, Plans/PlanDetails.tsx, Checkout/CheckoutForm.tsx, PaymentAttempts/PaymentAttemptPage.tsx, SubscriptionDetails/index.tsx, Subscriptions/SubscriptionsList.tsx, contexts/components/Plans.tsx
Components now treat annual pricing as optional: presence checks use truthiness (Boolean(...)) instead of numeric amount comparisons; some places use non-null assertions (!) with ESLint disables; checkout payload now uses paymentMethodId (local var renamed); fee/period-selection logic adjusted to rely on existence of annualMonthlyFee.
Billing utils
packages/clerk-js/src/utils/billing.ts
Added truthy guards for total_due_now, credit, and past_due before assigning totals; added inline comment about proration/total_due_after_free_trial.
Backend tests
packages/backend/src/tokens/__tests__/handshake.test.ts
Updated handshake test expectation for __clerk_api_version query param to the new API version.
UI tests
packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx, packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
Fixtures and assertions updated to use null for annualFee / annualMonthlyFee in scenarios representing absent annual pricing.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI component
  participant Model as BillingPlan.fromJSON
  participant Types as Types
  participant Checkout as Checkout flow

  UI->>Model: request plan JSON
  Note right of Model: annual_fee / annual_monthly_fee may be null\n(fromJSON logs values)
  Model-->>UI: BillingPlan (annualMonthlyFee may be null)

  alt annualMonthlyFee present
    UI->>UI: show annual toggle
    UI->>Checkout: use plan.annualMonthlyFee for annual pricing
  else annualMonthlyFee absent
    UI->>UI: hide annual toggle
    UI->>Checkout: fallback to plan.fee
  end

  UI->>Checkout: submit confirm checkout (paymentMethodId)
  Checkout-->>Types: ConfirmCheckoutParams includes paymentMethodId
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review focus:
    • packages/types/src/billing.ts and packages/types/src/json.ts for correct nullability and type propagation.
    • BillingPlan.fromJSON mapping and the stray console.log.
    • Places using non-null assertions (!) and ESLint disables — verify runtime safety and add guards if needed.
    • Checkout rename: paymentSourceIdpaymentMethodId (types, payloads, and form handling).
    • Tests updated to ensure both null and present annual fee cases are covered.

Poem

🐰 I hopped through plans both old and new,

some fees now null, some toggles too,
a version bumped, a tiny log,
payment named right — no more fog,
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Update the supported API version to 2025-10-01" is clearly related to a significant portion of the changeset. The PR includes updates to SUPPORTED_BAPI_VERSION and SUPPORTED_FAPI_VERSION constants from '2025-04-10' to '2025-10-01', corresponding test expectations, and a changeset entry documenting these version updates. The title accurately reflects this primary objective. However, the PR also includes additional changes not mentioned in the title, such as making annualFee and annualMonthlyFee nullable across multiple type definitions and UI components, renaming paymentSourceId to paymentMethodId, and updating corresponding UI logic. While these secondary changes are substantial, the title still meaningfully identifies the core driver of the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch elef/bill-1377-update-clerk-js-and-clerkbackend-to-use-the-new-api-versions

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

@panteliselef panteliselef marked this pull request as ready for review October 29, 2025 17:43
@panteliselef panteliselef changed the title feat(backend, clerk-js): Update the supported API version to 2025-10-01 [WIP] feat(backend, clerk-js): Update the supported API version to 2025-10-01 Oct 29, 2025
@panteliselef panteliselef changed the title [WIP] feat(backend, clerk-js): Update the supported API version to 2025-10-01 feat(backend, clerk-js): Update the supported API version to 2025-10-01 Oct 29, 2025
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: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fb49d29 and e783dd0.

📒 Files selected for processing (2)
  • packages/backend/src/constants.ts (1 hunks)
  • packages/clerk-js/src/core/constants.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/clerk-js/src/core/constants.ts
  • packages/backend/src/constants.ts
🔇 Additional comments (1)
packages/clerk-js/src/core/constants.ts (1)

57-57: Version update is properly integrated and tested.

The verification confirms that SUPPORTED_FAPI_VERSION = '2025-10-01' is correctly implemented:

  • The version is consistently passed as __clerk_api_version query parameter in all API requests via fapiClient.buildUrl()
  • Comprehensive test coverage (15+ test cases in fapiClient.test.ts) confirms the version is correctly appended to request URLs
  • Type definitions in @clerk/types are generically designed to handle versioned API responses without requiring version-specific schema switching

The frontend does not handle version-dependent response schema changes—this responsibility belongs to the backend, which validates and returns appropriate responses based on the __clerk_api_version parameter. The generic type system ensures compatibility across API versions.

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: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e783dd0 and 263b29b.

📒 Files selected for processing (1)
  • .changeset/hot-jars-smell.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.changeset/**

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Automated releases must use Changesets.

Files:

  • .changeset/hot-jars-smell.md
⏰ 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). (2)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 29, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7095

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7095

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7095

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7095

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7095

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7095

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@7095

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@7095

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7095

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7095

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7095

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7095

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7095

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7095

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@7095

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7095

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@7095

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7095

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7095

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7095

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@7095

@clerk/types

npm i https://pkg.pr.new/@clerk/types@7095

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7095

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7095

commit: ed3a125

@panteliselef panteliselef changed the title feat(backend, clerk-js): Update the supported API version to 2025-10-01 [WIP] feat(backend, clerk-js): Update the supported API version to 2025-10-01 Oct 29, 2025
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: 8

🧹 Nitpick comments (1)
packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx (1)

159-163: Consider adding non-null assertion for type safety.

The planFee calculation at line 162 may cause a TypeScript error because plan.annualMonthlyFee is inferred as BillingMoneyAmount | null, even though the logic ensures it's non-null when reached.

Consider updating the code to match the pattern in PricingTableDefault.tsx:

-                const planFee = !plan.annualMonthlyFee
-                  ? plan.fee
-                  : planPeriod === 'annual'
-                    ? plan.annualMonthlyFee
-                    : plan.fee;
+                const planFee = !plan.annualMonthlyFee
+                  ? plan.fee
+                  : planPeriod === 'annual'
+                    ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                      plan.annualMonthlyFee!
+                    : plan.fee;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 263b29b and 6ac7b4d.

📒 Files selected for processing (13)
  • packages/backend/src/tokens/__tests__/handshake.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/BillingPlan.ts (2 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts (1 hunks)
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx (1 hunks)
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx (2 hunks)
  • packages/types/src/billing.ts (1 hunks)
  • packages/types/src/json.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/backend/src/tokens/tests/handshake.test.ts
🧰 Additional context used
📓 Path-based instructions (9)
packages/clerk-js/src/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)

packages/clerk-js/src/ui/**/*.{ts,tsx}: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{jsx,tsx}: Use error boundaries in React components
Minimize re-renders in React components

**/*.{jsx,tsx}: Always use functional components with hooks instead of class components
Follow PascalCase naming for components: UserProfile, NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/react.mdc)

**/*.tsx: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering

Files:

  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
🧬 Code graph analysis (2)
packages/clerk-js/src/core/resources/BillingPlan.ts (2)
packages/types/src/billing.ts (1)
  • BillingMoneyAmount (615-632)
packages/clerk-js/src/utils/billing.ts (1)
  • billingMoneyAmountFromJSON (10-17)
packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (1)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • normalizeFormatted (27-29)
⏰ 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). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (12)
packages/clerk-js/src/core/resources/BillingPlan.ts (2)

11-12: LGTM: Nullable field declarations are correct.

Making annualFee and annualMonthlyFee nullable with default null properly reflects the API changes and allows plans without annual pricing options.


44-45: LGTM: Proper null-safe JSON deserialization.

The ternary operator correctly handles nullable annual pricing fields, setting them to null when absent from the API response.

packages/types/src/json.ts (1)

637-638: LGTM: Type definitions correctly reflect nullable annual pricing.

Making annual_fee and annual_monthly_fee nullable in the JSON type definitions properly aligns with the API version 2025-10-01 changes.

packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts (1)

37-37: LGTM: Presence-based check is safer.

Switching from plan.annualMonthlyFee.amount > 0 to Boolean(plan.annualMonthlyFee) is the correct approach for nullable fields. This avoids potential null reference errors and properly handles the updated data model.

packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (1)

377-378: LGTM: Safe presence check for switchability.

Using Boolean(subscription.plan.annualMonthlyFee) is the correct approach to check if annual pricing is available before allowing period switching.

packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx (2)

229-234: LGTM: Proper null handling with fallback.

This code correctly checks for annualMonthlyFee existence before using it, falling back to plan.fee when it's null. This is the safe pattern that should be used throughout the codebase.


336-364: LGTM: Conditional rendering based on existence.

Using plan.annualMonthlyFee in a conditional expression properly handles nullable fields. The toggle is only shown when annual pricing is available, with an appropriate notice displayed otherwise.

packages/types/src/billing.ts (1)

134-140: LGTM! Type definitions accurately reflect nullable annual pricing.

The type changes for annualFee and annualMonthlyFee to include | null are well-documented and align with the broader API version update. Since the interface is marked as @experimental, this change is appropriate.

packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1)

287-296: LGTM! Non-null assertion is safe in this context.

The fee calculation correctly handles nullable annualMonthlyFee. The non-null assertion on line 294 is safe because planSupportsAnnual is only true when annualMonthlyFee is truthy.

packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx (2)

63-63: Consistent with presence-based logic across the PR.

The change to Boolean(plan.annualMonthlyFee) aligns with the nullable type updates. This enables billing cycle controls when annualMonthlyFee exists, regardless of amount.


255-297: LGTM! Conditional rendering correctly handles nullable field.

The truthiness check plan.annualMonthlyFee ? (...) appropriately renders the annual billing notice only when annual pricing exists.

packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)

216-216: LGTM! Correctly checks for annual pricing eligibility.

The use of Boolean(plan?.annualMonthlyFee) with optional chaining safely checks whether a plan supports annual billing.

Comment on lines +35 to +39
console.log('data', {
fee: data.fee,
annual_fee: data.annual_fee,
annual_monthly_fee: data.annual_monthly_fee,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove debug console.log before merging.

Debug logging should not be committed to production code. This will clutter browser consoles and could expose internal data structures.

Apply this diff to remove the debug statement:

-    console.log('data', {
-      fee: data.fee,
-      annual_fee: data.annual_fee,
-      annual_monthly_fee: data.annual_monthly_fee,
-    });
-
     this.id = data.id;
📝 Committable suggestion

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

Suggested change
console.log('data', {
fee: data.fee,
annual_fee: data.annual_fee,
annual_monthly_fee: data.annual_monthly_fee,
});
this.id = data.id;
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/BillingPlan.ts around lines 35 to 39,
there is a debug console.log printing the fee/annual_fee/annual_monthly_fee
object; remove this debug statement before merging (delete the console.log call
and its object) or replace it with a non-debugging, environment-gated logging
mechanism if persistent logging is required, ensuring no sensitive/internal
structures are emitted to the browser console.

Comment on lines +42 to +46
const fee =
planPeriod === 'month'
? plan.fee
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plan.annualMonthlyFee!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe non-null assertion could cause runtime errors.

Same issue as in PaymentAttemptPage: using plan.annualMonthlyFee! without verification could throw if the field is null.

Apply this safer pattern:

-  const fee =
-    planPeriod === 'month'
-      ? plan.fee
-      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        plan.annualMonthlyFee!;
+  const fee =
+    planPeriod === 'month' || !plan.annualMonthlyFee
+      ? plan.fee
+      : plan.annualMonthlyFee;
📝 Committable suggestion

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

Suggested change
const fee =
planPeriod === 'month'
? plan.fee
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plan.annualMonthlyFee!;
const fee =
planPeriod === 'month' || !plan.annualMonthlyFee
? plan.fee
: plan.annualMonthlyFee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines 42
to 46, the code uses an unsafe non-null assertion plan.annualMonthlyFee! which
can throw at runtime if that field is null; replace the assertion with a guarded
access and explicit handling: check whether plan.annualMonthlyFee is defined
before using it (e.g., use a conditional/fallback value or throw a descriptive
error), or compute fee via a nullish coalescing/fallback expression so fee is
never assigned an unchecked null value; ensure TypeScript types reflect the
runtime check so there are no non-null assertions left.

Comment on lines 217 to +221
const fee =
subscriptionItem.planPeriod === 'month' ? subscriptionItem.plan.fee : subscriptionItem.plan.annualMonthlyFee;
subscriptionItem.planPeriod === 'month'
? subscriptionItem.plan.fee
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
subscriptionItem.plan.annualMonthlyFee!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe non-null assertion could cause runtime errors.

The code uses a non-null assertion (plan.annualMonthlyFee!) without first verifying that annualMonthlyFee exists. Since this field is now nullable (as per the type changes), this could throw a runtime error if the API returns null.

Apply this defensive pattern instead:

-  const fee =
-    subscriptionItem.planPeriod === 'month'
-      ? subscriptionItem.plan.fee
-      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        subscriptionItem.plan.annualMonthlyFee!;
+  const fee =
+    subscriptionItem.planPeriod === 'month' || !subscriptionItem.plan.annualMonthlyFee
+      ? subscriptionItem.plan.fee
+      : subscriptionItem.plan.annualMonthlyFee;
📝 Committable suggestion

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

Suggested change
const fee =
subscriptionItem.planPeriod === 'month' ? subscriptionItem.plan.fee : subscriptionItem.plan.annualMonthlyFee;
subscriptionItem.planPeriod === 'month'
? subscriptionItem.plan.fee
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
subscriptionItem.plan.annualMonthlyFee!;
const fee =
subscriptionItem.planPeriod === 'month' || !subscriptionItem.plan.annualMonthlyFee
? subscriptionItem.plan.fee
: subscriptionItem.plan.annualMonthlyFee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
around lines 217-221, the code uses a non-null assertion on
subscriptionItem.plan.annualMonthlyFee which is now nullable; replace the unsafe
assertion with a defensive check: if annualMonthlyFee is present use it,
otherwise fall back to a safe default (e.g., plan.fee, 0, or a user-friendly
placeholder) or throw/log a clear error before using the value so no runtime
null access occurs. Ensure the code handles both month and non-month periods
without the `!` operator and preserves type-safety.

const { name, annualMonthlyFee } = plan;

const planSupportsAnnual = annualMonthlyFee.amount > 0;
const planSupportsAnnual = Boolean(annualMonthlyFee);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify behavior change: zero-amount annual pricing.

The logic changed from checking annualMonthlyFee.amount > 0 to Boolean(annualMonthlyFee). This means plans with an annual option but zero cost (amount: 0) will now show the annual toggle, whereas previously they would not.

Run the following script to check if any plans in the codebase have annual pricing with zero amounts:


🏁 Script executed:

#!/bin/bash
# Search for test data or fixtures with annual pricing
rg -n -A5 -B5 'annualMonthlyFee|annual_monthly_fee' --type=ts --type=json --type=tsx

Length of output: 27


I'll retry with corrected ripgrep syntax and examine the actual file:


🏁 Script executed:

#!/bin/bash
# Search for annualMonthlyFee without specifying tsx type
rg -n -A5 -B5 'annualMonthlyFee|annual_monthly_fee'

Length of output: 74162


🏁 Script executed:

#!/bin/bash
# Also examine the actual file at line 285 with context
cat packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx | head -300 | tail -50

Length of output: 1579


🏁 Script executed:

#!/bin/bash
# Search for BillingMoneyAmount type definitions and mock data
rg -n 'BillingMoneyAmount|billingMoneyAmount' -g '*.ts' -g '*.tsx' -g '*.json'

Length of output: 7912


🏁 Script executed:

#!/bin/bash
# Look for test files or fixtures related to pricing
fd -e test.ts -e test.tsx -e mock.ts -e mock.tsx -e fixture.ts | xargs grep -l 'annualMonthlyFee\|annual' 2>/dev/null || echo "No test files found with pricing patterns"

Length of output: 585


🏁 Script executed:

#!/bin/bash
# Search for where pricing plans are created/mocked in the codebase
rg -n 'amount.*0|cost.*0|price.*0' packages/clerk-js/src/ui/components/PricingTable/ --type=ts

Length of output: 1428


🏁 Script executed:

#!/bin/bash
# Look at the imports and structure of the PricingTableDefault file
head -50 packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx

Length of output: 1542


Fix logic to check annualMonthlyFee?.amount > 0 instead of just Boolean(annualMonthlyFee).

Line 285 changed to check for object existence instead of positive amount. This breaks plans with zero-cost annual pricing—they will now show the annual toggle when they shouldn't. The test in pricing-footer-state.spec.ts line 108 ("does not show footer when switching period if annualMonthlyFee is 0") confirms the intended behavior: plans with annualMonthlyFee: { amount: 0, ... } must not be treated as supporting annual periods.

const planSupportsAnnual = Boolean(annualMonthlyFee?.amount > 0);

Note: This pattern also appears in Plans.tsx (lines 216, 329), pricing-footer-state.ts (line 37), and PricingTableMatrix.tsx (lines 63, 159)—verify whether those require similar fixes.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
around line 285 the logic was changed to Boolean(annualMonthlyFee) which treats
an object with amount: 0 as truthy; update the check to explicitly test that
annualMonthlyFee?.amount > 0 so only positive annual prices enable the annual
toggle; also search and update the same pattern in Plans.tsx (around lines 216,
329), pricing-footer-state.ts (around line 37), and PricingTableMatrix.tsx
(around lines 63, 159) to use annualMonthlyFee?.amount > 0 where appropriate to
preserve the test behavior that zero-cost annual plans do not support annual
periods.

Comment on lines +412 to +415
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
price: normalizeFormatted(subscription.plan.annualFee!.amountFormatted),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
currency: subscription.plan.annualFee!.currencySymbol,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe non-null assertion in localization keys.

The non-null assertions for subscription.plan.annualFee! are unsafe. If annualFee is null, this will throw at runtime when constructing the localization string.

Since this code path only executes when isSwitchable is true (which checks for annualMonthlyFee), you should also verify annualFee exists:

+      // Ensure annualFee exists before using it
+      if (!subscription.plan.annualFee) {
+        return null;
+      }
       isSwitchable
         ? {
             label:
               subscription.planPeriod === 'month'
                 ? localizationKeys('billing.switchToAnnualWithAnnualPrice', {
-                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                    price: normalizeFormatted(subscription.plan.annualFee!.amountFormatted),
-                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                    currency: subscription.plan.annualFee!.currencySymbol,
+                    price: normalizeFormatted(subscription.plan.annualFee.amountFormatted),
+                    currency: subscription.plan.annualFee.currencySymbol,
                   })

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx around
lines 412 to 415, the code uses non-null assertions on
subscription.plan.annualFee which can throw if annualFee is null; add a guard
that verifies subscription.plan.annualFee exists before accessing
amountFormatted and currencySymbol (e.g., if (!subscription.plan.annualFee)
return/skip building the localized string or provide safe fallbacks), and
replace the non-null assertions with safe access or validated locals so the
localization string is only built when annualFee is present.

Comment on lines +485 to +489
const fee =
subscription.planPeriod === 'month'
? subscription.plan.fee
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
subscription.plan.annualFee!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe non-null assertion in fee calculation.

Using subscription.plan.annualFee! without verification could throw if the field is null.

Apply this safer pattern:

-  const fee =
-    subscription.planPeriod === 'month'
-      ? subscription.plan.fee
-      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        subscription.plan.annualFee!;
+  const fee =
+    subscription.planPeriod === 'month' || !subscription.plan.annualFee
+      ? subscription.plan.fee
+      : subscription.plan.annualFee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx around
lines 485 to 489, the code uses a non-null assertion
subscription.plan.annualFee! which can throw if annualFee is null/undefined;
replace the assertion with a safe check — use optional chaining and a fallback
(e.g., subscription.plan.annualFee ?? 0 or another sensible default), or
explicitly handle the missing value (throw a descriptive error or return early).
Ensure fee is computed like: if planPeriod === 'month' use
subscription.plan.fee, otherwise use subscription.plan.annualFee ?? <fallback>
so no non-null assertion is needed.

Comment on lines +156 to +157
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fee = subscription.planPeriod === 'annual' ? subscription.plan.annualFee! : subscription.plan.fee;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe non-null assertion for annualFee.

Using subscription.plan.annualFee! without checking if it exists first could cause runtime errors when the field is null.

Apply this safer approach:

-  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-  const fee = subscription.planPeriod === 'annual' ? subscription.plan.annualFee! : subscription.plan.fee;
+  const fee = subscription.planPeriod === 'annual' && subscription.plan.annualFee 
+    ? subscription.plan.annualFee 
+    : subscription.plan.fee;
📝 Committable suggestion

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

Suggested change
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fee = subscription.planPeriod === 'annual' ? subscription.plan.annualFee! : subscription.plan.fee;
const fee = subscription.planPeriod === 'annual' && subscription.plan.annualFee
? subscription.plan.annualFee
: subscription.plan.fee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
around lines 156-157, the code uses a non-null assertion
subscription.plan.annualFee! which can throw if annualFee is null; change this
to safely handle absence by first checking if subscription.plan.annualFee is
defined (e.g., use optional chaining and a fallback or conditional): when
planPeriod is 'annual' use subscription.plan.annualFee ?? fallbackValue (or
handle the null case explicitly), otherwise use subscription.plan.fee; ensure
TypeScript types reflect the fallback or null-check so no non-null assertion is
used.

@panteliselef panteliselef force-pushed the elef/bill-1377-update-clerk-js-and-clerkbackend-to-use-the-new-api-versions branch from 6ac7b4d to 52a2409 Compare October 29, 2025 21:08
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

♻️ Duplicate comments (4)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)

42-46: Unsafe non-null assertion could cause runtime errors.

The non-null assertion plan.annualMonthlyFee! on line 46 is unsafe. Since annualMonthlyFee is now nullable (as seen in the type updates in packages/types/src/billing.ts), this will throw a runtime error if the field is null.

Apply the safer pattern suggested in the previous review:

-  const fee =
-    planPeriod === 'month'
-      ? plan.fee
-      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        plan.annualMonthlyFee!;
+  const fee =
+    planPeriod === 'month' || !plan.annualMonthlyFee
+      ? plan.fee
+      : plan.annualMonthlyFee;
packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (2)

412-415: Unsafe non-null assertion in localization keys.

The non-null assertions for subscription.plan.annualFee! on lines 413 and 415 are unsafe. If annualFee is null, this will throw at runtime.

As suggested in the previous review, add a guard before this code path:

+      // Ensure annualFee exists before using it
+      if (subscription.planPeriod === 'month' && !subscription.plan.annualFee) {
+        return [];
+      }
       isSwitchable
         ? {
             label:
               subscription.planPeriod === 'month'
                 ? localizationKeys('billing.switchToAnnualWithAnnualPrice', {
-                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                    price: normalizeFormatted(subscription.plan.annualFee!.amountFormatted),
-                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                    currency: subscription.plan.annualFee!.currencySymbol,
+                    price: normalizeFormatted(subscription.plan.annualFee.amountFormatted),
+                    currency: subscription.plan.annualFee.currencySymbol,
                   })

485-489: Unsafe non-null assertion in fee calculation.

Using subscription.plan.annualFee! without verification on line 489 could throw if the field is null.

Apply this safer pattern:

-  const fee =
-    subscription.planPeriod === 'month'
-      ? subscription.plan.fee
-      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        subscription.plan.annualFee!;
+  const fee =
+    subscription.planPeriod === 'month' || !subscription.plan.annualFee
+      ? subscription.plan.fee
+      : subscription.plan.annualFee;
packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1)

285-285: Critical: Incorrect logic allows zero-cost annual plans to show annual toggle.

Line 285 changed to check Boolean(annualMonthlyFee) instead of annualMonthlyFee.amount > 0. This breaks the intended behavior: plans with annual pricing set to 0 ({amount: 0, ...}) will now be treated as supporting annual periods when they shouldn't.

The test in pricing-footer-state.spec.ts line 108 explicitly expects: "does not show footer when switching period if annualMonthlyFee is 0".

-  const planSupportsAnnual = Boolean(annualMonthlyFee);
+  const planSupportsAnnual = Boolean(annualMonthlyFee && annualMonthlyFee.amount > 0);

Note: This same pattern also appears in:

  • Plans.tsx (lines 216, 329)
  • pricing-footer-state.ts (line 37)
  • PricingTableMatrix.tsx (lines 63, 159)

Verify and update those locations as well.

Based on learnings

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac7b4d and 52a2409.

📒 Files selected for processing (15)
  • packages/clerk-js/src/core/resources/BillingPlan.ts (2 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts (1 hunks)
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx (1 hunks)
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx (2 hunks)
  • packages/clerk-js/src/utils/billing.ts (1 hunks)
  • packages/types/src/billing.ts (1 hunks)
  • packages/types/src/json.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
  • packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
  • packages/clerk-js/src/core/resources/BillingPlan.ts
  • packages/clerk-js/src/ui/components/PricingTable/utils/pricing-footer-state.ts
🧰 Additional context used
📓 Path-based instructions (13)
packages/clerk-js/src/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)

packages/clerk-js/src/ui/**/*.{ts,tsx}: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{jsx,tsx}: Use error boundaries in React components
Minimize re-renders in React components

**/*.{jsx,tsx}: Always use functional components with hooks instead of class components
Follow PascalCase naming for components: UserProfile, NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
packages/{clerk-js,elements,themes}/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Visual regression testing should be performed for UI components.

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/utils/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/types/src/billing.ts
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/react.mdc)

**/*.tsx: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
**/*.test.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react.mdc)

**/*.test.{jsx,tsx}: Use React Testing Library
Test component behavior, not implementation
Use proper test queries
Implement proper test isolation
Use proper test coverage
Test component interactions
Use proper test data
Implement proper test setup
Use proper test cleanup
Implement proper test assertions
Use proper test structure

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
  • packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
🧬 Code graph analysis (1)
packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (1)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • normalizeFormatted (27-29)
⏰ 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). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx (1)

265-266: LGTM! Test data correctly reflects nullable annual pricing.

The change to null values for annualFee and annualMonthlyFee in the Free Plan scenario correctly aligns with the type updates that made these fields nullable. This ensures tests cover the case where annual pricing is not available.

packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx (1)

224-225: LGTM! Test data correctly reflects nullable annual pricing.

The changes to null values for annualFee and annualMonthlyFee properly test scenarios where annual pricing is unavailable. This aligns with the type updates in packages/types/src/billing.ts that made these fields nullable.

Also applies to: 258-259

packages/clerk-js/src/utils/billing.ts (1)

28-36: The original review comment is based on incorrect reasoning about JavaScript truthiness.

The truthy checks on lines 28, 31, and 34 will not exclude zero-amount values. Since BillingMoneyAmountJSON is an object (e.g., {amount: 0, amountFormatted: '0.00', ...}), and objects are truthy in JavaScript, these conditions will evaluate to true and the assignments will occur. The type definitions confirm these fields are required (not nullable) in BillingCheckoutTotalsJSON and BillingStatementTotalsJSON, and extensive test coverage demonstrates zero-amount objects are expected in the output.

The distinction between the original in check and the new truthy check only matters if the value is null, undefined, or a falsy scalar—not for objects with zero-amount properties inside them.

Likely an incorrect or invalid review comment.

Comment on lines +292 to +294
return planPeriod === 'annual'
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plan.annualMonthlyFee!
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe non-null assertion when annual pricing is enabled.

Line 294 uses plan.annualMonthlyFee! with a non-null assertion. If planSupportsAnnual is true but annualMonthlyFee is somehow null (e.g., data inconsistency), this will throw at runtime.

Consider a safer pattern:

-    return planPeriod === 'annual'
-      ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        plan.annualMonthlyFee!
-      : plan.fee;
+    if (planPeriod === 'annual' && plan.annualMonthlyFee) {
+      return plan.annualMonthlyFee;
+    }
+    return plan.fee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
around lines 292 to 294, the code uses an unsafe non-null assertion
plan.annualMonthlyFee! when planPeriod === 'annual'; instead, guard against a
missing annualMonthlyFee by checking for its presence (or using a nullish
fallback) before returning it — for example, return the annualPrice if defined,
otherwise fall back to a sensible default (like plan.monthlyFee or throw/log a
descriptive error) so the UI doesn't crash on inconsistent data.

const gridTemplateColumns = React.useMemo(() => `repeat(${plans.length + 1}, minmax(9.375rem,1fr))`, [plans.length]);

const renderBillingCycleControls = React.useMemo(() => plans.some(plan => plan.annualMonthlyFee.amount > 0), [plans]);
const renderBillingCycleControls = React.useMemo(() => plans.some(plan => Boolean(plan.annualMonthlyFee)), [plans]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Incorrect logic allows zero-cost annual plans to show billing controls.

Line 63 changed to check Boolean(plan.annualMonthlyFee) instead of verifying annualMonthlyFee.amount > 0. This means plans with zero-cost annual pricing ({amount: 0, ...}) will incorrectly display annual billing controls.

This is inconsistent with test expectations in pricing-footer-state.spec.ts which explicitly requires that zero-amount annual fees should not enable annual period support.

-  const renderBillingCycleControls = React.useMemo(() => plans.some(plan => Boolean(plan.annualMonthlyFee)), [plans]);
+  const renderBillingCycleControls = React.useMemo(
+    () => plans.some(plan => Boolean(plan.annualMonthlyFee && plan.annualMonthlyFee.amount > 0)),
+    [plans]
+  );
📝 Committable suggestion

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

Suggested change
const renderBillingCycleControls = React.useMemo(() => plans.some(plan => Boolean(plan.annualMonthlyFee)), [plans]);
const renderBillingCycleControls = React.useMemo(
() => plans.some(plan => Boolean(plan.annualMonthlyFee && plan.annualMonthlyFee.amount > 0)),
[plans]
);
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
around line 63, the billing-cycle control detection uses
Boolean(plan.annualMonthlyFee) which incorrectly treats a zero-amount annual fee
as enabled; change the predicate to check the numeric amount so only positive
annual fees enable the control (e.g., use plan.annualMonthlyFee?.amount > 0 or
equivalent null-safe check) so plans with {amount: 0} do not show annual billing
controls.

Comment on lines +159 to +163
const planFee = !plan.annualMonthlyFee
? plan.fee
: planPeriod === 'annual'
? plan.annualMonthlyFee
: plan.fee;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Fee calculation should verify amount > 0 for consistency.

The fee calculation on lines 159-163 uses presence checks (!plan.annualMonthlyFee) but should also verify the amount is greater than zero to match the expected behavior across the codebase.

-                const planFee = !plan.annualMonthlyFee
+                const planFee = !plan.annualMonthlyFee || plan.annualMonthlyFee.amount === 0
                   ? plan.fee
                   : planPeriod === 'annual'
                     ? plan.annualMonthlyFee
                     : plan.fee;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx
around lines 159-163, the fee selection currently uses a truthy presence check
for plan.annualMonthlyFee but should ensure the amount is greater than zero;
update the ternary to explicitly check that plan.annualMonthlyFee > 0 (or
Number(plan.annualMonthlyFee) > 0) before choosing it, otherwise fall back to
plan.fee, preserving the existing planPeriod logic.

Comment on lines +377 to 379
((subscription.planPeriod === 'month' && Boolean(subscription.plan.annualMonthlyFee)) ||
subscription.planPeriod === 'annual') &&
subscription.status !== 'past_due';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Inconsistent annual pricing checks could cause runtime errors.

Line 377 checks for annualMonthlyFee to determine if the plan is switchable, but lines 412-415 and 485-489 use non-null assertions on annualFee without verification. These are different fields, and one being present doesn't guarantee the other exists.

Either:

  1. Guard both fields before the switchable/fee logic executes, or
  2. Use the same field consistently throughout
  const isSwitchable =
    ((subscription.planPeriod === 'month' && Boolean(subscription.plan.annualMonthlyFee)) ||
      subscription.planPeriod === 'annual') &&
    subscription.status !== 'past_due';
+
+ // Verify annualFee exists before building actions that use it
+ if (isSwitchable && subscription.planPeriod === 'month' && !subscription.plan.annualFee) {
+   return [];
+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx around
lines 377-379 and 412-415 and 485-489, the code inconsistently checks
subscription.plan.annualMonthlyFee but later uses non-null assertions on
subscription.plan.annualFee; this mismatch can cause runtime errors. Update the
logic so the same field is used consistently (preferably choose one canonical
field across the component) or explicitly guard both fields before any
dereference: change the switchable/fee condition to verify both annualMonthlyFee
and annualFee exist where required, and replace non-null assertions with safe
conditional access or early returns when those fields are missing.

Comment on lines +38 to +39
// WHY `total_due_after_free_trial` and why `proration`

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify the purpose of this comment.

The comment suggests uncertainty about total_due_after_free_trial and proration fields. If these fields are part of the API response but not being handled, document why they're excluded or create a task to implement them.

🤖 Prompt for AI Agents
In packages/clerk-js/src/utils/billing.ts around lines 38 to 39, the inline
comment "// WHY `total_due_after_free_trial` and why `proration`" is vague;
replace it with a clear explanation stating whether those fields appear in the
API response and why they are intentionally ignored (e.g., not used by current
billing flow), or if they must be supported, add a TODO with a link/ID to a
tracker ticket to implement handling. Include what the fields represent, the
decision (exclude or implement), and next steps (either remove/comment as
explained or add task to implement parsing/logic).

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/types/src/billing.ts (1)

691-703: Changeset must document the breaking change with explicit migration guidance.

The changeset file (./.changeset/hot-jars-smell.md) only states "Update the supported API version to 2025-10-01" but fails to document the breaking change: renaming paymentSourceId to paymentMethodId in ConfirmCheckoutParams.

While the codebase has been correctly updated (no references to the old parameter remain), the changeset lacks the necessary migration guidance that users need to update their code. This differs from the pattern established in v4.93.0, which documented similar billing API renames with clear before/after examples.

Update the changeset to include:

  • Explicit mention of the parameter rename: paymentSourceIdpaymentMethodId
  • Migration guidance: "Rename paymentSourceId to paymentMethodId in all checkout confirm() calls"
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52a2409 and ed3a125.

📒 Files selected for processing (2)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2 hunks)
  • packages/types/src/billing.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/types/src/billing.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/types/src/billing.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/types/src/billing.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/types/src/billing.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/types/src/billing.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/types/src/billing.ts
⏰ 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). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan

Comment on lines +134 to +140
* The annual price of the plan or `null` if the plan is not annual.
*/
annualFee: BillingMoneyAmount;
annualFee: BillingMoneyAmount | null;
/**
* The effective monthly price when billed annually.
* The effective monthly price when billed annually or `null` if the plan is not annual.
*/
annualMonthlyFee: BillingMoneyAmount;
annualMonthlyFee: BillingMoneyAmount | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that consumer code was updated per previous review findings.

The nullable type change for annualFee and annualMonthlyFee is correct, but a previous review identified specific locations with non-null assertions and unsafe Boolean checks that will break with this change.

Ensure all flagged locations have been fixed before merging:

  • 7 non-null assertion sites (e.g., SubscriptionsList.tsx:157, SubscriptionDetails/index.tsx:413,415,489)
  • 3 unsafe Boolean checks (e.g., PricingTableMatrix.tsx:63, pricing-footer-state.ts:37)

Based on learnings


🏁 Script executed:

#!/bin/bash
# Verify that non-null assertions and unsafe Boolean checks have been addressed

echo "=== Checking for remaining non-null assertions on annual fees ==="
rg -n 'annualFee!' --type=ts -C2

echo ""
echo "=== Checking for remaining non-null assertions on annualMonthlyFee ==="
rg -n 'annualMonthlyFee!' --type=ts -C2

echo ""
echo "=== Checking for unsafe Boolean checks without null guards ==="
rg -nP 'if\s*\([^)]*\.(annualFee|annualMonthlyFee)\)' --type=ts -C2

echo ""
echo "=== Checking for direct property access without null checks ==="
rg -n '\.(annualFee|annualMonthlyFee)\.(amount|amountFormatted|currency)' --type=ts -C3

Length of output: 4581


Address unsafe non-null assertions in consumer code to properly handle nullable annual fees.

The nullable type changes for annualFee and annualMonthlyFee are correct, but consumer code still uses non-null assertions (!) that bypass TypeScript's type safety. These should be replaced with proper null checks or type guards:

  • SubscriptionsList.tsx:157: subscription.plan.annualFee!
  • SubscriptionDetails/index.tsx:413, 415, 489: annualFee! and property access
  • PricingTableDefault.tsx:294: plan.annualMonthlyFee!
  • PaymentAttemptPage.tsx:221: plan.annualMonthlyFee!
  • CheckoutForm.tsx:46: plan.annualMonthlyFee!

While runtime logic (e.g., planPeriod === 'annual') ensures these values exist in practice, the type system doesn't capture these guarantees. Refactor to use discriminated unions, type predicates, or explicit null checks instead of non-null assertions.

🤖 Prompt for AI Agents
In packages/types/src/billing.ts around lines 134-140, avoid forcing non-null
with `!` in consumers by either making the Plan type a discriminated union
(e.g., separate MonthlyPlan and AnnualPlan where annualFee/annualMonthlyFee
exist only on AnnualPlan with a literal period discriminator) or keep the
nullable fields but update the listed consumer files to narrow the type before
access: replace `!` usages with explicit null checks or type guards (e.g., if
(plan.period === 'annual') { /* safe to access annualFee/annualMonthlyFee */ })
so TypeScript can verify safety and remove the non-null assertions in
SubscriptionsList.tsx:157, SubscriptionDetails/index.tsx:413,415,489,
PricingTableDefault.tsx:294, PaymentAttemptPage.tsx:221, and
CheckoutForm.tsx:46.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants