Skip to content

Conversation

panteliselef
Copy link
Member

@panteliselef panteliselef commented Sep 12, 2025

Description

Vanilla JS

// Before
const { getState, subscribe, confirm, start, clear, finalize  } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" })
getState().isStarting
getState().isConfirming
getState().error
getState().checkout
getState().fetchStatus
getState().status

// After
const { checkout, errors, fetchStatus } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" })
checkout.plan // null or defined based on `checkout.status`
checkout.status
checkout.start
checkout.confirm

React

// Before
const { id, plan, status, start, confirm, paymentSource } = useCheckout({ planId: "xxx", planPeriod: "annual" })

// After
const { checkout, errors, fetchStatus } = usecCheckout({ planId: "xxx", planPeriod: "annual" })
checkout.plan // null or defined based on `checkout.status`
checkout.status
checkout.start
checkout.confirm

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

  • New Features

    • Revamped checkout experience with a responsive, signal-based flow for faster updates.
    • Clearer error handling, including a new “generic_error” state and improved global error messages.
    • Smoother checkout completion animation with refined hover effects.
  • Refactor

    • Migrated checkout logic and hooks to a signal-based architecture for better performance and reliability.
    • Updated payment-related components to use leaner, function-free data shapes.
  • Tests

    • Removed legacy checkout manager tests; aligned type tests with the new API.
  • Chores

    • Added changeset for minor version bumps across packages.

Copy link

changeset-bot bot commented Sep 12, 2025

🦋 Changeset detected

Latest commit: 3a82f64

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

This PR includes changesets to release 22 packages
Name Type
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/clerk-react Minor
@clerk/types Minor
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/elements Patch
@clerk/expo-passkeys 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
@clerk/vue Patch
@clerk/localizations Patch
@clerk/themes 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

Copy link

vercel bot commented Sep 12, 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 Sep 15, 2025 3:19pm

Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

Walkthrough

Introduces a signal-based checkout flow replacing the previous instance/manager model. Adds CheckoutFuture, createSignals, and related types; updates Clerk/React integration to return CheckoutSignalValue; refactors UI to consume errors/fetchStatus from signals; modifies component typings; removes legacy manager and tests; adds a changeset for minor bumps.

Changes

Cohort / File(s) Summary
Changeset
\.changeset/angry-maps-mix.md
Adds minor version bumps for four packages; no code changes.
Core checkout wiring (Clerk JS)
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/modules/checkout/instance.ts, packages/clerk-js/src/core/resources/CommerceCheckout.ts
Switches to signal-based checkout: __experimental_checkout returns CheckoutSignalValue; adds CheckoutFuture and createSignals; introduces cache by checkout key; removes prior instance lifecycle.
Types: checkout and utilities
packages/types/src/clerk.ts, packages/types/src/commerce.ts, packages/types/src/utils.ts
Adds CheckoutSignalValue, CheckoutSignal, CheckoutFutureResource types; removes v1 instance types; adds utility types ForceNull, RemoveFunctions, Prettify.
React integration and state proxy
packages/react/src/isomorphicClerk.ts, packages/react/src/stateProxy.ts
Gates __experimental_checkout on loaded state with StateProxy fallback; adds checkoutSignal to StateProxy building a gated CheckoutSignalValue.
Shared React hook and types
packages/shared/src/react/hooks/useCheckout.ts, packages/shared/src/react/commerce.tsx
Refactors useCheckout to return CheckoutSignalValue via useSyncExternalStore; expands PaymentElementProviderProps.checkout to accept CheckoutFutureResource.
Checkout UI components
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx, packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx, packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx, packages/clerk-js/src/ui/components/Checkout/index.tsx, packages/clerk-js/src/ui/components/Checkout/parts.tsx
Updates to consume errors and fetchStatus from signals; adjusts status union to include generic_error; moves ring animation into SuccessRing; form logic aligns with checkout.start/confirm; minor prop value change to generic_error.
Payment source component
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
Changes prop type to RemoveFunctions<CommercePaymentSourceResource>; no behavior change.
Legacy manager removal and tests
packages/clerk-js/src/core/modules/checkout/manager.ts, packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts, packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
Removes manager implementation and its tests; skips a types test; aligns with new signal-based approach.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Hook as useCheckout()
  participant ReactCtx as Clerk Instance (Context)
  participant ClerkJS as clerk-js (__experimental_checkout)
  participant Inst as createCheckoutInstance (cache)
  participant Signals as createSignals
  participant Future as CheckoutFuture
  participant Billing as Clerk.billing

  App->>Hook: useCheckout({ planId, planPeriod, for })
  Hook->>ReactCtx: get clerk instance
  Hook->>ClerkJS: __experimental_checkout(options)
  alt Cached by key
    ClerkJS->>Inst: createCheckoutInstance(...)
    Inst-->>Hook: cached computed signal (CheckoutSignalValue)
  else Not cached
    ClerkJS->>Inst: createCheckoutInstance(...)
    Inst->>Signals: createSignals()
    Inst->>Future: new CheckoutFuture(signals, config)
    Inst-->>Hook: computed signal (CheckoutSignalValue)
  end
  App->>Hook: subscribe via useSyncExternalStore
  App->>Hook: read CheckoutSignalValue
  App->>Future: checkout.start()
  Future->>Billing: start checkout (async)
  Billing-->>Future: result or error
  Future->>Signals: update resource/error/fetchStatus
  App->>Future: checkout.confirm(params)
  Future->>Billing: confirm (async)
  Billing-->>Future: result or error
  Future->>Signals: update resource/error/fetchStatus
Loading
sequenceDiagram
  autonumber
  actor App
  participant Iso as isomorphicClerk.__experimental_checkout
  participant SP as StateProxy.checkoutSignal
  participant CJ as clerkjs.__experimental_checkout

  App->>Iso: __experimental_checkout(options)
  alt clerk loaded and clerkjs present
    Iso->>CJ: delegate(options)
    CJ-->>App: CheckoutSignalValue
  else not loaded
    Iso->>SP: fallback(options)
    SP-->>App: Gated CheckoutSignalValue proxy
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 title "feat(clerk-js,shared): Align useCheckout with future hooks" is concise and accurately summarizes the primary intent of the changeset—migrating useCheckout/Clerk.checkout to a new signal-based hook shape—so it reflects the main change developers will care about when scanning the history. It is specific, free of noise, and tied to the actual modifications in the diff.

Poem

I twitch my ears at signals’ gleam,
A checkout flow now swift and clean.
No manager burrows, caches hum,
With future hops, the updates come.
Errors corralled, the carrots align—
I thump, approve: this warren’s design. 🥕✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch elef/bill-1098-update-checkout-public

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

Copy link

pkg-pr-new bot commented Sep 12, 2025

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/elements

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

@clerk/clerk-expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/clerk-react

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

@clerk/react-router

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

@clerk/remix

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/themes

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

@clerk/types

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

@clerk/upgrade

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

@clerk/vue

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

commit: 3a82f64

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

Caution

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

⚠️ Outside diff range comments (3)
packages/shared/src/react/commerce.tsx (2)

257-260: Bug: amount uses || and breaks when totalDueNow.amount is 0

If totalDueNow.amount is 0, the expression falls back to grandTotal erroneously. Use nullish coalescing.

-          amount: checkout.totals.totalDueNow?.amount || checkout.totals.grandTotal.amount,
+          amount: checkout.totals.totalDueNow?.amount ?? checkout.totals.grandTotal.amount,

299-323: Type contract: return async functions in not-ready branch

submit/reset must return Promise types. Throwing sync breaks declared signature.

 const throwLibsMissingError = () => {
   throw new Error(
     'Clerk: Unable to submit, Stripe libraries are not yet loaded. Be sure to check `isFormReady` before calling `submit`.',
   );
 };
+
+const throwLibsMissingErrorAsync = async (): Promise<never> => {
+  throwLibsMissingError();
+};
@@
   if (!isProviderReady) {
     return {
-      submit: throwLibsMissingError,
-      reset: throwLibsMissingError,
+      submit: throwLibsMissingErrorAsync,
+      reset: throwLibsMissingErrorAsync,
       isFormReady: false,
       provider: undefined,
       isProviderReady: false,
     };
   }

Also applies to: 371-389

packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)

61-81: Stale memo: missing errors in dependency array.

The memo reads errors but only depends on fetchStatus. Include errors (or errors.global) to recompute when errors change.

-  }, [fetchStatus]);
+  }, [fetchStatus, errors, errors?.global]);

Optionally, depend on the derived codes:

-  }, [fetchStatus]);
+  }, [fetchStatus, errors?.global?.map(e => e.code).join(',')]);
🧹 Nitpick comments (23)
packages/types/src/utils.ts (2)

44-49: Constrain ForceNull to objects

Prevents accidental use with primitives and narrows intent without breaking current usages.

-export type ForceNull<T> = {
+export type ForceNull<T extends object> = {
   [K in keyof T]: null;
 };

127-129: Avoid any in type-level function detection

Prefer unknown to align with “no any” guideline; keeps identical behavior.

-export type RemoveFunctions<T extends object> = {
-  [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K];
-};
+export type RemoveFunctions<T extends object> = {
+  [K in keyof T as T[K] extends (...args: unknown[]) => unknown ? never : K]: T[K];
+};
packages/shared/src/react/commerce.tsx (1)

85-93: Optional: log initialization failures for Stripe payment source

Silent catch makes diagnosing setup issues hard. Consider logging at debug level.

-  initializePaymentSource().catch(() => {
-      // ignore errors
-  });
+  initializePaymentSource().catch((e) => {
+    // Optional: surface in dev without spamming prod logs
+    if (process.env.NODE_ENV !== 'production') {
+      console.debug('initializePaymentSource failed', e);
+    }
+  });
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx (1)

167-175: Remove no-op useRef; rely on effect

useRef(() => setter(text)) never runs; it only stores a function. Keep the effect; tighten types.

-const useSetAndSync = (text: LocalizationKey, setter: (a: any) => void) => {
-  useRef(() => {
-    setter(text);
-  });
-
-  useEffect(() => {
-    setter(text);
-  }, [text, setter]);
-};
+const useSetAndSync = (text: LocalizationKey, setter: (v: LocalizationKey) => void) => {
+  useEffect(() => {
+    setter(text);
+  }, [text, setter]);
+};
packages/react/src/isomorphicClerk.ts (1)

743-747: Add explicit return types for public API methods and keep style consistent.

Per guidelines, explicitly annotate return types for public APIs. Also consider using the same if/else style used elsewhere for consistency.

Apply:

-  __experimental_checkout = (...args: Parameters<Clerk['__experimental_checkout']>) => {
+  __experimental_checkout = (
+    ...args: Parameters<Clerk['__experimental_checkout']>
+  ): ReturnType<Clerk['__experimental_checkout']> => {
     return this.loaded && this.clerkjs
       ? this.clerkjs.__experimental_checkout(...args)
       : this.#stateProxy.checkoutSignal(...args);
   };

-  __experimental_checkoutV2 = (...args: Parameters<Clerk['__experimental_checkoutV2']>) => {
+  __experimental_checkoutV2 = (
+    ...args: Parameters<Clerk['__experimental_checkoutV2']>
+  ): ReturnType<Clerk['__experimental_checkoutV2']> => {
     return this.loaded && this.clerkjs
       ? this.clerkjs.__experimental_checkoutV2(...args)
       : this.#stateProxy.checkoutSignalV2(...args);
   };
packages/clerk-js/src/ui/components/Checkout/parts.tsx (3)

46-50: Avoid @ts-expect-error by narrowing error metadata.

Replace the suppression with a safe meta extractor.

-    const _error = errors?.global?.find(e => e.code === 'invalid_plan_change');
-    // @ts-expect-error - meta is not yet defined
-    return _error?.meta?.plan;
+    const _error = errors?.global?.find(e => e.code === 'invalid_plan_change');
+    const meta =
+      _error && typeof _error === 'object' && 'meta' in (_error as any)
+        ? (( _error as any ).meta as { plan?: any })
+        : undefined;
+    return meta?.plan;

Consider formalizing the meta type in packages/types for this code, to remove casts entirely.


52-56: Same metadata narrowing for isPlanUpgradePossible.

-    const _error = errors?.global?.find(e => e.code === 'invalid_plan_change');
-    // @ts-expect-error - meta is not yet defined
-    return _error?.meta?.isPlanUpgradePossible || false;
+    const _error = errors?.global?.find(e => e.code === 'invalid_plan_change');
+    const meta =
+      _error && typeof _error === 'object' && 'meta' in (_error as any)
+        ? (( _error as any ).meta as { isPlanUpgradePossible?: boolean })
+        : undefined;
+    return meta?.isPlanUpgradePossible ?? false;

119-121: Handle possible start() errors.

checkout.start() returns an object with { error }. Consider logging or surfacing it.

-          onSuccess={() => void checkout.start()}
+          onSuccess={async () => {
+            const { error } = await checkout.start();
+            if (error) {
+              // TODO: surface via card context or alert
+              console.error(error);
+            }
+          }}
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (3)

143-146: Avoid throwing in render path; gate UI instead.

Throwing here bubbles to error boundaries; prefer gating the elements that require confirmation.

-  if (checkout.status !== 'needs_confirmation') {
-    throw new Error('Checkout not found');
-  }
+  if (checkout.status !== 'needs_confirmation') {
+    return {
+      payWithExistingPaymentSource: () => Promise.resolve(),
+      addPaymentSourceAndPay: () => Promise.resolve(),
+      payWithTestCard: () => Promise.resolve(),
+    };
+  }

151-159: Type-safe error handling without @ts-expect-error.

handleError already branches on known error types; no need for a suppression.

-    const { error } = await checkout.confirm(params);
-
-    if (error) {
-      // @ts-expect-error - error is not an Error
-      handleError(error, [], card.setError);
+    const { error } = await checkout.confirm(params);
+    if (error) {
+      handleError(error as unknown, [], card.setError);
     } else {
       onSubscriptionComplete?.();
     }

369-371: Variable naming mismatch with behavior.

shouldDefaultBeUsed renders the selector when true. Rename for clarity.

-    const shouldDefaultBeUsed = totalDueNow.amount === 0 || !freeTrialEndsAt;
+    const showPaymentSourceSelector = totalDueNow.amount === 0 || !freeTrialEndsAt;

And update usages accordingly.

packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)

66-78: Error code mapping is fine. Consider early-return for perf/readability.

Small readability tweak:

-    if (errors.global) {
+    if (errors.global?.length) {
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

110-114: Type assertion could hide type mismatches.

The type assertion on lines 112-113 could mask potential type mismatches between the cache entry and the expected return type.

Consider using a type guard or narrowing the type more safely:

-  if (cache.has(checkoutKey)) {
-    return (
-      cache.get(checkoutKey) as { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }
-    ).signals.computedSignal();
-  }
+  const cached = cache.get(checkoutKey);
+  if (cached) {
+    return cached.signals.computedSignal();
+  }
packages/shared/src/react/hooks/useCheckout.ts (3)

195-203: Commented-out code should be removed.

Lines 186-193 contain commented-out code that appears to be an alternative implementation. This should be removed for code cleanliness.

-  // const signal = useCallback(() => {
-  //   return clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization });
-  // }, [
-  //   // user?.id, organization?.id,
-  //   planId,
-  //   planPeriod,
-  //   forOrganization,
-  // ]);
-
   const signal = useMemo(() => {

98-104: Commented-out dependencies in useMemo.

Lines 100 and 199 have commented-out dependencies (user?.id, organization?.id). If these are not needed, remove them completely. If they might be needed, add a TODO comment explaining why.

   const manager = useMemo(() => {
     return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
-  }, [
-    // user?.id, organization?.id,
-    planId,
-    planPeriod,
-    forOrganization,
-  ]);
+  }, [planId, planPeriod, forOrganization, clerk]);

Also for line 197-202:

   const signal = useMemo(() => {
     return () => clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization });
-  }, [
-    // user?.id, organization?.id,
-    planId,
-    planPeriod,
-    forOrganization,
-  ]);
+  }, [planId, planPeriod, forOrganization, clerk]);

106-120: Large block of commented-out code should be removed.

Lines 106-120 contain a large block of commented-out code. This should be removed for code cleanliness.

Remove the entire commented-out block from lines 106-120.

packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (1)

25-43: Potential memory leak with requestAnimationFrame.

While the cleanup function is present, there's a potential issue where animationRef.current could be overwritten before the previous animation is cancelled if the effect runs multiple times quickly.

   useEffect(() => {
     if (!canHover) {
       return;
     }
+    // Cancel any existing animation before starting a new one
+    if (animationRef.current) {
+      cancelAnimationFrame(animationRef.current);
+    }
     const animate = () => {
       setCurrentPosition(prev => {
         const amt = 0.15;
         const x = lerp(prev.x, positionX, amt);
         const y = lerp(prev.y, positionY, amt);
         return { x, y };
       });
       animationRef.current = requestAnimationFrame(animate);
     };
     animationRef.current = requestAnimationFrame(animate);
     return () => {
       if (animationRef.current) {
         cancelAnimationFrame(animationRef.current);
       }
     };
   }, [positionX, positionY, canHover]);
packages/types/src/commerce.ts (1)

1404-1410: Consider using a const assertion for status values.

The discriminated union pattern is good, but the status values could be defined as a const to ensure consistency.

Consider defining the status values as a const:

const CHECKOUT_STATUS = {
  NEEDS_INITIALIZATION: 'needs_initialization',
  NEEDS_CONFIRMATION: 'needs_confirmation', 
  COMPLETED: 'completed'
} as const;

type CheckoutStatus = typeof CHECKOUT_STATUS[keyof typeof CHECKOUT_STATUS];

This would ensure consistency across the codebase and make refactoring easier.

packages/react/src/stateProxy.ts (1)

128-179: Remove large block of commented-out code.

Lines 128-179 contain a large block of commented-out code. This should be removed for code cleanliness.

Remove the entire commented-out buildCheckoutProxy implementation from lines 128-179.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

133-164: Avoid any in request body; keep types intact

params is already ConfirmCheckoutParams. Prefer passing the typed value (or unknown) instead of any.

Apply this diff:

-          body: params as any,
+          body: params as unknown,

254-285: Simplify and normalize error parsing

Current logic toggles between null and [] for raw/global. Consider consistent shape for easier consumers.

Apply this diff:

 function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
-  const parsedErrors: CheckoutSignalValue['errors'] = {
-    raw: null,
-    global: null,
-  };
-
-  if (!error) {
-    return parsedErrors;
-  }
-
-  if (!isClerkAPIResponseError(error)) {
-    parsedErrors.raw = [];
-    parsedErrors.global = [];
-    return parsedErrors;
-  }
-
-  error.errors.forEach(error => {
-    if (parsedErrors.raw) {
-      parsedErrors.raw.push(error);
-    } else {
-      parsedErrors.raw = [error];
-    }
-
-    if (parsedErrors.global) {
-      parsedErrors.global.push(error);
-    } else {
-      parsedErrors.global = [error];
-    }
-  });
-
-  return parsedErrors;
+  if (!error) {
+    return { raw: null, global: null };
+  }
+  if (!isClerkAPIResponseError(error)) {
+    return { raw: [], global: [] };
+  }
+  const list = [...error.errors];
+  return { raw: list, global: [...list] };
 }

287-305: Public API JSDoc is missing

createSignals and CheckoutFuture are part of the public surface for Checkout V2. Add concise JSDoc with @experimental, shape, and lifecycle notes.

I can draft JSDoc blocks aligned with the types package if you want.

Also applies to: 306-363


21-93: Remove large commented-out blocks

Dead/commented code adds noise and confuses readers. Please delete these blocks instead of commenting out.

Also applies to: 167-253

📜 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 54b4b5a and 492f569.

📒 Files selected for processing (19)
  • .changeset/angry-maps-mix.md (1 hunks)
  • packages/clerk-js/src/core/clerk.ts (4 hunks)
  • packages/clerk-js/src/core/modules/checkout/instance.ts (3 hunks)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (5 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (4 hunks)
  • packages/clerk-js/src/ui/components/Checkout/index.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx (4 hunks)
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx (1 hunks)
  • packages/react/src/experimental.ts (1 hunks)
  • packages/react/src/isomorphicClerk.ts (1 hunks)
  • packages/react/src/stateProxy.ts (6 hunks)
  • packages/shared/src/react/commerce.tsx (2 hunks)
  • packages/shared/src/react/hooks/index.ts (1 hunks)
  • packages/shared/src/react/hooks/useCheckout.ts (4 hunks)
  • packages/types/src/clerk.ts (4 hunks)
  • packages/types/src/commerce.ts (2 hunks)
  • packages/types/src/utils.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{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/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.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/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
packages/**/*.{ts,tsx,d.ts}

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

Packages should export TypeScript types alongside runtime code

Files:

  • packages/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
packages/**/index.{js,ts}

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

Use tree-shaking friendly exports

Files:

  • packages/shared/src/react/hooks/index.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/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.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/shared/src/react/hooks/index.ts
  • packages/react/src/experimental.ts
  • packages/shared/src/react/commerce.tsx
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
**/index.ts

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

Use index.ts files for clean imports but avoid deep barrel exports

Avoid barrel files (index.ts re-exports) as they can cause circular dependencies

Files:

  • packages/shared/src/react/hooks/index.ts
**/*.{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/shared/src/react/commerce.tsx
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/shared/src/react/commerce.tsx
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
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/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
.changeset/**

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

Automated releases must use Changesets.

Files:

  • .changeset/angry-maps-mix.md
🧬 Code graph analysis (13)
packages/shared/src/react/commerce.tsx (2)
packages/types/src/commerce.ts (2)
  • CheckoutFutureResource (1412-1427)
  • CommerceCheckoutResource (1195-1253)
packages/shared/src/react/hooks/useCheckout.ts (1)
  • useCheckout (78-165)
packages/react/src/stateProxy.ts (3)
packages/types/src/commerce.ts (3)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
  • CheckoutFutureResource (1412-1427)
packages/types/src/clerk.ts (3)
  • __experimental_CheckoutInstance (129-136)
  • NullableCheckoutSignal (94-96)
  • Clerk (204-934)
packages/clerk-js/src/core/clerk.ts (1)
  • Clerk (201-2957)
packages/react/src/isomorphicClerk.ts (2)
packages/clerk-js/src/core/clerk.ts (1)
  • Clerk (201-2957)
packages/types/src/clerk.ts (1)
  • Clerk (204-934)
packages/clerk-js/src/ui/components/Checkout/parts.tsx (2)
packages/shared/src/react/hooks/index.ts (1)
  • useCheckout (16-16)
packages/shared/src/react/hooks/useCheckout.ts (1)
  • useCheckout (78-165)
packages/clerk-js/src/core/modules/checkout/instance.ts (2)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • CheckoutFuture (306-362)
  • createSignals (287-304)
packages/types/src/clerk.ts (2)
  • __experimental_CheckoutOptions (113-117)
  • NullableCheckoutSignal (94-96)
packages/clerk-js/src/core/clerk.ts (1)
packages/types/src/clerk.ts (2)
  • __experimental_CheckoutOptions (113-117)
  • NullableCheckoutSignal (94-96)
packages/types/src/commerce.ts (1)
packages/types/src/utils.ts (2)
  • RemoveFunctions (127-129)
  • ForceNull (47-49)
packages/shared/src/react/hooks/useCheckout.ts (4)
packages/shared/src/react/contexts.tsx (2)
  • useCheckoutContext (118-118)
  • useClerkInstanceContext (117-117)
packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
  • useCheckoutContext (10-37)
packages/types/src/clerk.ts (1)
  • CheckoutSignalValue (79-92)
packages/clerk-js/src/core/modules/checkout/manager.ts (1)
  • subscribe (127-132)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)
packages/clerk-js/src/ui/customizables/index.ts (1)
  • Box (15-15)
packages/clerk-js/src/ui/customizables/elementDescriptors.ts (1)
  • descriptors (568-568)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
  • FieldError (7-20)
packages/types/src/commerce.ts (1)
  • CheckoutFutureResource (1412-1427)
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (3)
packages/react/src/stateProxy.ts (1)
  • checkout (270-274)
packages/shared/src/react/hooks/index.ts (1)
  • useCheckout (16-16)
packages/shared/src/react/hooks/useCheckout.ts (1)
  • useCheckout (78-165)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (6)
packages/react/src/stateProxy.ts (1)
  • checkout (270-274)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • plan (329-331)
  • totals (335-337)
packages/types/src/commerce.ts (1)
  • ConfirmCheckoutParams (1162-1188)
packages/clerk-js/src/ui/utils/errorHandler.ts (1)
  • handleError (69-91)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • usePaymentMethods (32-40)
packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
  • usePaymentMethods (9-21)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (79-92)
  • CheckoutSignal (98-100)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
packages/clerk-js/src/utils/runAsyncResourceTask.ts (1)
  • runAsyncResourceTask (8-30)
⏰ 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). (4)
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (16)
packages/clerk-js/src/ui/components/Checkout/index.tsx (1)

43-45: Confirm status literal alignment ("generic_error") — confirmed

CheckoutPage.FetchStatus declares/accepts 'generic_error' (packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx) and index.tsx uses it; underlying hook/types still use 'error' (packages/shared/src/react/hooks/useCheckout.ts, packages/types/src/clerk.ts) but CheckoutPage maps hook errors → 'generic_error', so no migration required.

packages/shared/src/react/hooks/index.ts (1)

17-18: Verified: useCheckoutV2 exported and no barrel-induced cycle

useCheckout.ts defines and exports useCheckoutV2 and packages/shared/src/react/hooks/index.ts re-exports it; useCheckout imports useOrganization/useUser via relative files (not via the hooks barrel), so the barrel does not introduce a circular import.

packages/react/src/experimental.ts (1)

23-24: LGTM: re-export wiring is correct

Named re-export keeps tree-shaking friendly surface and matches shared aliasing.

packages/react/src/isomorphicClerk.ts (1)

737-741: Good gating fallback for v1 checkout.

Delegating to ClerkJS when loaded and falling back to StateProxy is consistent with the rest of the class.

packages/clerk-js/src/ui/components/Checkout/parts.tsx (1)

1-1: Hook migration looks correct.

Importing the v2 hook under the existing alias preserves local usage.

packages/types/src/clerk.ts (5)

25-26: LGTM on adding CheckoutFutureResource import.


58-58: LGTM on FieldError import for error modeling.


65-75: Error model shape is sensible.

Structured raw and global arrays look good.


94-101: Confirm intended return shape: is __experimental_checkoutV2 returning a function (signal) or a snapshot?

You define CheckoutSignal, but __experimental_CheckoutFunctionV2 returns NullableCheckoutSignal (a value), leaving CheckoutSignal unused. If the API is meant to return a callable signal, change the type:

-type __experimental_CheckoutFunctionV2 = (options: __experimental_CheckoutOptions) => NullableCheckoutSignal;
+type __experimental_CheckoutFunctionV2 = (options: __experimental_CheckoutOptions) => CheckoutSignal;

Otherwise, remove CheckoutSignal to avoid confusion. Please verify across implementations.


928-934: API surface addition looks good; ensure public JSDoc matches behavior.

Docstring matches the experimental nature.

packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)

12-17: Double-check cleanup removal.

clear() was removed from the cleanup. If v2 lifecycle no longer needs explicit clearing, fine; otherwise consider adding a v2-equivalent to avoid stale state between mounts.

packages/clerk-js/src/core/modules/checkout/instance.ts (1)

92-125: Missing explicit return type annotation.

The function createCheckoutInstanceV2 is missing an explicit return type annotation, which goes against the coding guidelines for TypeScript files.

-function createCheckoutInstanceV2(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal {
+function createCheckoutInstanceV2(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal {

Actually, the return type is already specified. The issue is resolved.

packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (1)

17-157: LGTM! Well-structured component extraction.

The extraction of the SuccessRing component improves code organization and reusability. The animation logic is properly encapsulated with appropriate cleanup.

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

1356-1402: LGTM! Well-structured type definitions for V2 checkout.

The new CheckoutFutureProperties interface and related types are properly documented and follow TypeScript best practices with appropriate use of utility types.

packages/react/src/stateProxy.ts (1)

203-253: Missing explicit return type annotations for private methods.

Several private methods are missing explicit return type annotations, which goes against the coding guidelines.

Add explicit return types to the private methods:

-  private gateListenerMethod<T extends object, K extends keyof T>(
+  private gateListenerMethod<T extends object, K extends keyof T>(
     getTarget: () => T,
     key: T[K] extends (...args: any[]) => void ? K : never,
   ): T[K] {

Actually, the return type T[K] is already specified. The methods have proper return type annotations.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)

108-131: Constructor now accepts null: verify consumer assumptions

Exposing an instance with many undefined fields until start() completes may break consumers that expect non-null values. Ensure all call sites read getters only after status !== 'needs_initialization', or add guards/JSDoc clarifying lifecycle.

Comment on lines +1 to +8
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
---

wip
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

Replace placeholder body with actionable release notes

The changeset message is "wip". Add a concise summary, migration notes, and experimental flags to produce useful release notes for consumers.

Apply:

- 
-wip
+Introduce experimental Checkout V2 (signals-based).
+
+– Add __experimental_checkoutV2 (Clerk JS) and __experimental_useCheckoutV2 (React/shared).
+– UI: switch CheckoutPage.FetchStatus fallback to "generic_error".
+– Shared: PaymentElementProvider accepts CheckoutFutureResource.
+– Types: add NullableCheckoutSignal, CheckoutFutureResource, ForceNull, RemoveFunctions.
+
+Migration
+– Prefer __experimental_* V2 APIs; V1 remains available.
+– If you relied on "error" FetchStatus, handle "generic_error".
📝 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
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
---
wip
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
---
Introduce experimental Checkout V2 (signals-based).
– Add __experimental_checkoutV2 (Clerk JS) and __experimental_useCheckoutV2 (React/shared).
– UI: switch CheckoutPage.FetchStatus fallback to "generic_error".
– Shared: PaymentElementProvider accepts CheckoutFutureResource.
– Types: add NullableCheckoutSignal, CheckoutFutureResource, ForceNull, RemoveFunctions.
Migration
– Prefer __experimental_* V2 APIs; V1 remains available.
– If you relied on "error" FetchStatus, handle "generic_error".
🤖 Prompt for AI Agents
In .changeset/angry-maps-mix.md lines 1-8 the body is a placeholder "wip" and
must be replaced with actionable release notes; update the file by replacing
"wip" with a concise summary of the changes (1–2 sentences), add any migration
notes or breaking changes steps if applicable, and list experimental flags or
opt-in notes under a short "notes" section so consumers know impact and how to
opt in; keep the existing YAML package header intact and ensure the body is
plain text with clear sections like Summary, Migration, and Experimental flags.

/**
* Create a checkout instance with the given options
*/
const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
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

Cache not being cleared on user/organization changes.

The module-level cache persists across user sessions and organization changes, which could lead to stale data being returned when a user signs out and signs back in or switches organizations.

Add cache invalidation when the user or organization context changes. Consider moving the cache to a Clerk instance property or implementing a cache invalidation mechanism:

+// Clear cache when user or organization changes
+export function clearCheckoutCache() {
+  cache.clear();
+}

Additionally, consider adding lifecycle hooks to clear the cache when the user signs out or the active organization changes.

📝 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 cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
/**
* Clear the module-level checkout cache.
*
* Call this when the authenticated user or active organization changes to avoid
* returning stale checkout data across sessions. This should be invoked from
* higher-level lifecycle hooks (e.g. on sign-out or organization switch).
*
* @public
*/
export function clearCheckoutCache(): void {
cache.clear();
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/modules/checkout/instance.ts around line 22, the
module-level cache Map is persisting across user sessions and org changes
causing stale data; replace it with an instance-scoped cache (attach the Map to
the Clerk instance or the checkout module instance) or implement explicit
invalidation: remove the module-level declaration, create the Map on the Clerk
instance, update all accesses to use this.instance.cache, and wire lifecycle
hooks/events (signOut, activeOrganization change) to call cache.clear(); ensure
type signatures remain correct and any tests or callers are updated to use the
instance-scoped cache.

Comment on lines +95 to +30
const clerk = useClerkInstanceContext();

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

Missing dependency clerk in useMemo hooks.

Both useCheckout and useCheckoutV2 are missing clerk in their dependency arrays for the useMemo hooks. This could lead to stale closures if the clerk instance changes.

For useCheckout (line 97-104):

   const manager = useMemo(() => {
     return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
-  }, [planId, planPeriod, forOrganization]);
+  }, [planId, planPeriod, forOrganization, clerk]);

For useCheckoutV2 (line 195-202):

   const signal = useMemo(() => {
     return () => clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization });
-  }, [planId, planPeriod, forOrganization]);
+  }, [planId, planPeriod, forOrganization, clerk]);

Also applies to: 184-185

🤖 Prompt for AI Agents
packages/shared/src/react/hooks/useCheckout.ts around lines 95-106 and 180-205:
Several useMemo hooks in useCheckout (approx. lines 97-104) and useCheckoutV2
(approx. lines 184-185 and 195-202) omit the clerk variable from their
dependency arrays, risking stale closures if the clerk instance changes; add
clerk to each affected useMemo dependency array (alongside existing deps), and
ensure any lint warnings are addressed by including any other values referenced
inside the memo callbacks so the dependency lists accurately reflect all
external values used.

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

♻️ Duplicate comments (5)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)

3-13: Import missing CheckoutSignal and NullableCheckoutSignal types to properly type the computed signal

The @ts-expect-error comment on line 291 indicates that CheckoutSignal is not defined. Import the missing types from @clerk/types and properly type the computed signal to remove the need for type suppression.

Apply this diff to fix the type imports and usage:

 import type {
   CheckoutFutureResourceLax,
   CheckoutSignalValue,
+  CheckoutSignal,
+  NullableCheckoutSignal,
   CommerceCheckoutJSON,
   CommerceCheckoutResource,
   CommerceCheckoutTotals,
   CommercePayerResource,
   CommerceSubscriptionPlanPeriod,
   ConfirmCheckoutParams,
   CreateCheckoutParams,
 } from '@clerk/types';

291-301: Remove @ts-expect-error and properly type the computed signal

The computed signal needs proper typing with the imported CheckoutSignal types. Also remove the redundant property shorthand and commented console.log.

Apply this diff:

-  // @ts-expect-error - CheckoutSignal is not yet defined
-  const computedSignal: CheckoutSignal = computed(() => {
+  const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => {
     const resource = resourceSignal().resource;
     const error = errorSignal().error;
     const fetchStatus = fetchSignal().status;
 
-    // console.log('computedSignal', resource, error, fetchStatus);
-
     const errors = errorsToParsedErrors(error);
-    return { errors: errors, fetchStatus, checkout: resource };
+    return { errors, fetchStatus, checkout: resource };
   });

356-362: Add null checks for billing client to prevent runtime errors

The start() method doesn't validate that CommerceCheckout.clerk.billing exists before calling startCheckout. This could result in assigning undefined to this.resource.

Apply this diff to add proper validation:

 async start(): Promise<{ error: unknown }> {
   return runAsyncResourceTask(this.signals, async () => {
-    const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout;
-    this.resource = checkout;
+    const billing = CommerceCheckout.clerk.billing;
+    if (!billing?.startCheckout) {
+      throw new Error('Checkout cannot start: billing client is not available.');
+    }
+    const checkout = await billing.startCheckout(this.config);
+    if (!checkout) {
+      throw new Error('Checkout could not be initialized.');
+    }
+    this.resource = checkout as CommerceCheckout;
     this.signals.resourceSignal({ resource: this });
   });
 }

364-369: Validate resource initialization before confirming checkout

The confirm() method should check that the resource has been initialized with a valid ID before attempting confirmation.

Apply this diff:

 async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> {
   return runAsyncResourceTask(this.signals, async () => {
+    if (!this.resource?.id) {
+      throw new Error('Cannot confirm checkout before initialization.');
+    }
     await this.resource.confirm(params);
     this.signals.resourceSignal({ resource: this });
   });
 }

372-392: Fix unbalanced signal batching in runAsyncResourceTask

The current implementation has duplicate startBatch() calls and misplaced endBatch() calls that can break signal batching invariants.

Apply this diff to fix the batching:

 async function runAsyncResourceTask<T>(
   signals: ReturnType<typeof createSignals>,
   task: () => Promise<T>,
 ): Promise<{ result?: T; error: unknown }> {
   startBatch();
   signals.errorSignal({ error: null });
   signals.fetchSignal({ status: 'fetching' });
   endBatch();
-  startBatch();
   try {
     const result = await task();
     return { result, error: null };
   } catch (err) {
     signals.errorSignal({ error: err });
-    endBatch();
     return { error: err };
   } finally {
+    startBatch();
     signals.fetchSignal({ status: 'idle' });
-    endBatch();
+    endBatch();
   }
 }
🧹 Nitpick comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

21-93: Remove or extract commented-out code block to documentation

Lines 21-93 contain a large block of commented-out code. This appears to be legacy implementation or experimental code that should either be removed entirely or moved to documentation if it serves as a reference.

Consider removing this entire commented block as it adds unnecessary noise to the codebase and can cause confusion about which implementation is current.


167-233: Remove additional commented-out code block

Lines 167-233 contain another large block of commented experimental code. This should be removed to maintain code cleanliness.


254-285: Simplify error transformation logic in errorsToParsedErrors

The current implementation has redundant conditionals when building the arrays. The logic can be simplified.

Apply this diff to simplify the error transformation:

 function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
   const parsedErrors: CheckoutSignalValue['errors'] = {
     raw: null,
     global: null,
   };
 
   if (!error) {
     return parsedErrors;
   }
 
   if (!isClerkAPIResponseError(error)) {
     parsedErrors.raw = [];
     parsedErrors.global = [];
     return parsedErrors;
   }
 
-  error.errors.forEach(error => {
-    if (parsedErrors.raw) {
-      parsedErrors.raw.push(error);
-    } else {
-      parsedErrors.raw = [error];
-    }
-
-    if (parsedErrors.global) {
-      parsedErrors.global.push(error);
-    } else {
-      parsedErrors.global = [error];
-    }
-  });
+  parsedErrors.raw = [...error.errors];
+  parsedErrors.global = [...error.errors];
 
   return parsedErrors;
 }

352-354: Consider adding null safety for planPeriodStart

The planPeriodStart getter returns undefined when not set, which might be inconsistent with other nullable properties.

Consider returning null instead of undefined for consistency:

 get planPeriodStart() {
-  return this.resource.planPeriodStart;
+  return this.resource.planPeriodStart ?? null;
 }
📜 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 492f569 and 0b2e23f.

📒 Files selected for processing (3)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx (10 hunks)
  • packages/types/src/commerce.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/types/src/commerce.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/Checkout/__tests__/Checkout.test.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/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.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/ui/components/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.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/ui/components/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.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/ui/components/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{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/Checkout/__tests__/Checkout.test.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/Checkout/__tests__/Checkout.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/Checkout/__tests__/Checkout.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/Checkout/__tests__/Checkout.test.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.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/Checkout/__tests__/Checkout.test.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/Checkout/__tests__/Checkout.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/Checkout/__tests__/Checkout.test.tsx
🧬 Code graph analysis (2)
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx (1)
packages/clerk-js/src/ui/elements/Drawer.tsx (1)
  • Drawer (561-570)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (79-92)
  • CheckoutSignal (98-100)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
packages/clerk-js/src/utils/runAsyncResourceTask.ts (1)
  • runAsyncResourceTask (8-30)
packages/react/src/stateProxy.ts (1)
  • checkout (270-274)
⏰ 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). (6)
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (5)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)

108-108: Good: Constructor now accepts null for initialization

The change to allow null initialization aligns well with the new CheckoutFuture pattern that starts with an uninitialized state.


348-350: Good addition: paymentSource getter with null fallback

The addition of the paymentSource getter with a null fallback provides consistent API access.

packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx (3)

34-34: Good: Fixed hanging promises in tests

The change from unresolved promises to mockResolvedValue({} as any) properly prevents tests from hanging and ensures consistent test behavior.

Also applies to: 70-70, 138-138, 170-170, 212-212, 238-238, 260-260, 284-284


479-569: Well-structured test for subscription start date display

The new test properly validates the display of subscription start dates for downgrades, with comprehensive mock data setup.


571-659: Good test coverage for payment method display

The test properly validates that payment method details (Visa ⋯ 4242) are displayed in the completed checkout stage.


const freeTrialEndsAt = new Date('2025-08-19');

console.log('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2);
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

Remove debug console.log statement

Line 320 contains a console.log statement that should be removed before merging.

-    console.log('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2);
📝 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('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2);
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
around line 320, there is a debug console.log statement logging
fixtures.clerk.__experimental_checkoutV2; remove that console.log entirely from
the test file so no debug output remains in the test suite, then run tests to
ensure nothing relies on the log.

pathRoot: '/',
reload: jest.fn(),
},
planPeriodStart: new Date('2025-08-19'),
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

Type inconsistency: planPeriodStart should be a number

According to the CommerceCheckoutJSON type definition, plan_period_start should be a Unix timestamp (number), not a Date object.

Apply this diff to fix the type:

-      planPeriodStart: new Date('2025-08-19'),
+      planPeriodStart: new Date('2025-08-19').getTime() / 1000,
📝 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
planPeriodStart: new Date('2025-08-19'),
planPeriodStart: new Date('2025-08-19').getTime() / 1000,
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
around line 547, planPeriodStart is currently set to a Date object which
violates the CommerceCheckoutJSON type that requires a Unix timestamp number;
replace the Date with a numeric Unix timestamp (seconds) — e.g., use
Math.floor(new Date('2025-08-19').getTime() / 1000) or an equivalent numeric
literal so planPeriodStart is a number, not a Date.

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

♻️ Duplicate comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

2-14: Remove ts-expect-error; import and use proper CheckoutSignal types

computedSignal is suppressed and CheckoutSignal isn’t imported. Import CheckoutSignal and NullableCheckoutSignal, type the computed, and drop the redundant errors: errors.

 import type {
   CheckoutFutureResourceLax,
   CheckoutSignalValue,
+  CheckoutSignal,
+  NullableCheckoutSignal,
   CommerceCheckoutJSON,
   CommerceCheckoutResource,
   CommerceCheckoutTotals,
   CommercePayerResource,
   CommerceSubscriptionPlanPeriod,
   ConfirmCheckoutParams,
   CreateCheckoutParams,
 } from '@clerk/types';
 import { computed, endBatch, signal, startBatch } from 'alien-signals';
@@
 export const createSignals = () => {
   const resourceSignal = signal<{ resource: CheckoutFuture | null }>({ resource: null });
   const errorSignal = signal<{ error: unknown }>({ error: null });
   const fetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' });
-  // @ts-expect-error - CheckoutSignal is not yet defined
-  const computedSignal: CheckoutSignal = computed(() => {
+  const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => {
     const resource = resourceSignal().resource;
     const error = errorSignal().error;
     const fetchStatus = fetchSignal().status;
-
-    // console.log('computedSignal', resource, error, fetchStatus);
-
     const errors = errorsToParsedErrors(error);
-    return { errors: errors, fetchStatus, checkout: resource };
+    return { errors, fetchStatus, checkout: resource };
   });
 
   return { resourceSignal, errorSignal, fetchSignal, computedSignal };
 };

Also applies to: 287-304


357-369: Guard missing billing client and falsy checkout before assigning to resource

Without a billing client or if it returns a falsy value, this.resource becomes undefined, breaking follow-ups.

   async start(): Promise<{ error: unknown }> {
     return this.runAsyncResourceTask(
       'start',
       async () => {
-        const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout;
-        this.resource = checkout;
+        const billing = CommerceCheckout.clerk.billing;
+        if (!billing?.startCheckout) {
+          throw new Error('Checkout cannot start: billing client is not available.');
+        }
+        const checkout = await billing.startCheckout(this.config);
+        if (!checkout) {
+          throw new Error('Checkout could not be initialized.');
+        }
+        this.resource = checkout as CommerceCheckout;
       },
       () => {
         this.resource = new CommerceCheckout(null);
         this.signals.resourceSignal({ resource: this });
       },
     );
   }

371-375: Prevent confirm before initialization (missing resource.id/payer)

this.resource may lack an id/payer before start(), leading to invalid paths or runtime errors.

   async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> {
-    return this.runAsyncResourceTask('confirm', async () => {
-      await this.resource.confirm(params);
-    });
+    return this.runAsyncResourceTask('confirm', async () => {
+      if (!this.resource?.id) {
+        throw new Error('Cannot confirm checkout before initialization.');
+      }
+      await this.resource.confirm(params);
+    });
   }

382-414: Fix unbalanced batching (double endBatch on catch path)

Current flow calls endBatch() in both catch and finally for the same startBatch(), breaking batching invariants.

   const operationPromise = (async () => {
     startBatch();
     signals.errorSignal({ error: null });
     signals.fetchSignal({ status: 'fetching' });
     // signals.resourceSignal({ resource: null });
     beforeTask?.();
     endBatch();
-    startBatch();
     try {
+      startBatch();
       await task();
       signals.resourceSignal({ resource: resource });
-      return { error: null };
-    } catch (err) {
-      signals.errorSignal({ error: err });
-      endBatch();
-      return { error: err };
-    } finally {
-      pendingOperations.delete(operationType);
-      signals.fetchSignal({ status: 'idle' });
       endBatch();
+      return { error: null };
+    } catch (err) {
+      startBatch();
+      signals.errorSignal({ error: err });
+      signals.fetchSignal({ status: 'idle' });
+      endBatch();
+      return { error: err };
+    } finally {
+      pendingOperations.delete(operationType);
+      // fetch status already set in try/catch;
+      // no batching needed here
     }
   })();
🧹 Nitpick comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

133-151: Retry comment doesn’t match config

With initialDelay: 2000, maxDelayBetweenRetries: 2000, factor: 1.1, delays are ~2s (capped), not “2s, 4s, 6s, 8s”. Update the comment.

-    // This will retry up to 3 times with an increasing delay
-    // It retries at 2s, 4s, 6s and 8s
+    // Retries up to ~3–4 times with ~2s delay (capped by maxDelayBetweenRetries)

167-253: Remove large commented-out legacy blocks

These commented classes/functions add noise and risk drift. Delete or move to a design doc.


377-386: Narrow operationType to a union

Constrain to 'start' | 'confirm' for safer dedupe semantics.

-  private runAsyncResourceTask<T>(operationType: string, task: () => Promise<T>, beforeTask?: () => void) {
+  private runAsyncResourceTask<T>(operationType: 'start' | 'confirm', task: () => Promise<T>, beforeTask?: () => void) {
     return createRunAsyncResourceTask(this, this.signals, this.pendingOperations)(operationType, task, beforeTask);
   }
@@
-): <T>(operationType: string, task: () => Promise<T>, beforeTask?: () => void) => Promise<{ error: unknown }> {
+): <T>(operationType: 'start' | 'confirm', task: () => Promise<T>, beforeTask?: () => void) => Promise<{ error: unknown }> {

287-304: Add JSDoc and explicit return types for public APIs

createSignals and CheckoutFuture are exported; add JSDoc and explicit return types to meet project guidelines.

Also applies to: 306-380

📜 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 0b2e23f and b26c58f.

📒 Files selected for processing (1)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 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/resources/CommerceCheckout.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/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/CommerceCheckout.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/resources/CommerceCheckout.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/resources/CommerceCheckout.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/resources/CommerceCheckout.ts
🧬 Code graph analysis (1)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (79-92)
  • CheckoutSignal (98-100)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
⏰ 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). (17)
  • GitHub Check: Integration Tests (quickstart, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (tanstack-react-router, chrome)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Unit Tests (18, --filter=@clerk/astro --filter=@clerk/backend --filter=@clerk/express --filter=@c...
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Static analysis
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan

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/clerk-js/src/core/modules/checkout/instance.ts (1)

22-55: Adopt instance-scoped cache in create function.

Switch to per-instance map; add invalidation hook elsewhere (on signOut/org change).

-function createCheckoutInstance(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal {
+function createCheckoutInstance(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal {
@@
-  const checkoutKey = cacheKey({
+  const checkoutKey = cacheKey({
     userId: clerk.user.id,
     orgId: forOrganization === 'organization' ? clerk.organization?.id : undefined,
     planId,
     planPeriod,
   });
 
-  if (cache.has(checkoutKey)) {
-    return (
-      cache.get(checkoutKey) as { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }
-    ).signals.computedSignal();
-  }
+  const byInstance = getCache(clerk);
+  if (byInstance.has(checkoutKey)) {
+    return (byInstance.get(checkoutKey) as { signals: ReturnType<typeof createSignals> }).signals.computedSignal();
+  }
 
   const signals = createSignals();
@@
-  cache.set(checkoutKey, { resource: checkout, signals });
+  byInstance.set(checkoutKey, { resource: checkout, signals });
   return signals.computedSignal();
 }
♻️ Duplicate comments (10)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)

350-353: Initialize from known list; drop @ts-expect-error.

Resolve the incoming paymentSource by id from paymentSources.

-    const [selectedPaymentSource, setSelectedPaymentSource] = useState<CommercePaymentSourceResource | undefined>(
-      // @ts-expect-error - paymentSource is missing functions
-      paymentSource || paymentSources.find(p => p.isDefault),
-    );
+    const [selectedPaymentSource, setSelectedPaymentSource] = useState<CommercePaymentSourceResource | undefined>(() => {
+      const byId = paymentSource ? paymentSources.find(p => p.id === paymentSource.id) : undefined;
+      return byId || paymentSources.find(p => p.isDefault);
+    });

29-33: Guard totals to prevent null-deref.

totals may be null; subsequent reads (Lines 35-113) will throw.

-  const { plan, totals, isImmediatePlanChange, planPeriod, freeTrialEndsAt } = checkout;
-
-  if (!plan) {
+  const { plan, totals, isImmediatePlanChange, planPeriod, freeTrialEndsAt } = checkout;
+
+  if (!plan || !totals) {
     return null;
   }
packages/shared/src/react/hooks/useCheckout.ts (1)

31-39: Include clerk in deps to avoid stale closure.

Ensures the signal re-binds if the Clerk instance swaps.

-  }, [
-    // user?.id, organization?.id,
-    planId,
-    planPeriod,
-    forOrganization,
-  ]);
+  }, [planId, planPeriod, forOrganization, clerk]);
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

17-18: Module-level cache leaks across users/orgs; scope to Clerk instance.

Use a WeakMap keyed by Clerk and clear on sign-out/org switch.

-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
+type CacheEntry = { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> };
+const cache = new WeakMap<Clerk, Map<CheckoutKey, CacheEntry>>();
+
+function getCache(clerk: Clerk) {
+  let m = cache.get(clerk);
+  if (!m) {
+    m = new Map<CheckoutKey, CacheEntry>();
+    cache.set(clerk, m);
+  }
+  return m;
+}
packages/types/src/clerk.ts (2)

73-90: Fix JSDoc: replace SignIn with Checkout throughout.

Docs still reference SignIn; should say Checkout to match the new API.

-/**
- * The value returned by the `useSignInSignal` hook.
- */
+/**
+ * The value returned by the Checkout signal (`__experimental_checkout`).
+ */
 export interface CheckoutSignalValue {
   /**
-   * Represents the errors that occurred during the last fetch of the parent resource.
+   * Represents the errors that occurred during the last fetch of the Checkout resource.
    */
   errors: GlobalErrors;
   /**
-   * The fetch status of the underlying `SignIn` resource.
+   * The fetch status of the underlying Checkout resource.
    */
   fetchStatus: 'idle' | 'fetching';
   /**
-   * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows.
+   * An instance representing the currently active Checkout, with APIs designed for custom flows.
    */
   checkout: CheckoutFutureResource;
 }

105-105: Align __experimental_CheckoutFunction to return the signal function (CheckoutSignal)

Return the signal function, not its value.

  • Change type in packages/types/src/clerk.ts:
-type __experimental_CheckoutFunction = (options: __experimental_CheckoutOptions) => NullableCheckoutSignal;
+type __experimental_CheckoutFunction = (options: __experimental_CheckoutOptions) => CheckoutSignal;
  • Update runtime signature in packages/clerk-js/src/core/clerk.ts (currently __experimental_checkout(...): NullableCheckoutSignal) to return CheckoutSignal.
  • Verify and adjust call sites that expect the value vs the signal function (observed in packages/shared/src/react/hooks/useCheckout.ts and packages/react/src/isomorphicClerk.ts). Run: rg -n --hidden '__experimental_checkout' -g '!/node_modules/' -C2
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

3-14: Remove ts-expect-error; import and type the computed signal correctly.

Import CheckoutSignal/NullableCheckoutSignal and type computed; also use property shorthand.

 import type {
   CheckoutFutureResourceLax,
   CheckoutSignalValue,
+  CheckoutSignal,
+  NullableCheckoutSignal,
   CommerceCheckoutJSON,
   CommerceCheckoutResource,
   CommerceCheckoutTotals,
   CommercePayerResource,
   CommerceSubscriptionPlanPeriod,
   ConfirmCheckoutParams,
   CreateCheckoutParams,
 } from '@clerk/types';
@@
-  // @ts-expect-error - CheckoutSignal is not yet defined
-  const computedSignal: CheckoutSignal = computed(() => {
+  const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => {
     const resource = resourceSignal().resource;
     const error = errorSignal().error;
     const fetchStatus = fetchSignal().status;
 
     const errors = errorsToParsedErrors(error);
-    return { errors: errors, fetchStatus, checkout: resource };
+    return { errors, fetchStatus, checkout: resource };
   });

Also applies to: 127-139


195-207: Guard against missing billing client or falsy checkout in start().

Avoid assigning undefined to this.resource; return a clear error.

   async start(): Promise<{ error: unknown }> {
     return this.runAsyncResourceTask(
       'start',
       async () => {
-        const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout;
-        this.resource = checkout;
+        const billing = CommerceCheckout.clerk.billing;
+        if (!billing?.startCheckout) {
+          throw new Error('Checkout cannot start: billing client is not available.');
+        }
+        const checkout = await billing.startCheckout(this.config);
+        if (!checkout) {
+          throw new Error('Checkout could not be initialized.');
+        }
+        this.resource = checkout as CommerceCheckout;
       },
       () => {
         this.resource = new CommerceCheckout(null);
         this.signals.resourceSignal({ resource: this });
       },
     );
   }

209-213: Prevent confirm() before initialization.

Add precondition so we don’t hit an invalid path with undefined id.

   async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> {
-    return this.runAsyncResourceTask('confirm', async () => {
+    return this.runAsyncResourceTask('confirm', async () => {
+      if (!this.resource?.id) {
+        throw new Error('Cannot confirm checkout before initialization.');
+      }
       await this.resource.confirm(params);
     });
   }

220-258: Fix unbalanced batching in async task runner.

Remove extra startBatch/endBatch; end once in finally.

   return async (operationType, task, beforeTask?: () => void) => {
@@
-    const operationPromise = (async () => {
-      startBatch();
+    const operationPromise = (async () => {
+      startBatch();
       signals.errorSignal({ error: null });
       signals.fetchSignal({ status: 'fetching' });
       // signals.resourceSignal({ resource: null });
       beforeTask?.();
-      endBatch();
-      startBatch();
+      endBatch();
       try {
         await task();
         signals.resourceSignal({ resource: resource });
         return { error: null };
       } catch (err) {
         signals.errorSignal({ error: err });
-        endBatch();
         return { error: err };
       } finally {
         pendingOperations.delete(operationType);
-        signals.fetchSignal({ status: 'idle' });
-        endBatch();
+        startBatch();
+        signals.fetchSignal({ status: 'idle' });
+        endBatch();
       }
     })();
🧹 Nitpick comments (12)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (4)

21-22: Consider adding a fallback for server-side rendering.

The media query check could potentially cause hydration mismatches if the server and client have different hover capabilities.

Consider using a more robust SSR-safe approach:

-  const canHover =
-    typeof window === 'undefined' ? true : window.matchMedia('(hover: hover) and (pointer: fine)').matches;
+  const [canHover, setCanHover] = useState(false);
+  
+  useEffect(() => {
+    setCanHover(window.matchMedia('(hover: hover) and (pointer: fine)').matches);
+  }, []);

54-54: Remove TypeScript ignore comment.

The @ts-ignore comment should be replaced with proper typing or a more specific suppression.

-      // @ts-ignore - viewBox is a valid prop for svg
       viewBox='0 0 512 512'

Consider using a type assertion or extending the Box component's prop types to properly support SVG attributes when used with as='svg'.


46-48: Verify unique ID generation performance.

Multiple useId() calls within the same component could be consolidated or optimized.

Consider generating a single base ID and deriving the others:

-  const maskId1 = useId();
-  const maskId2 = useId();
-  const maskId3 = useId();
+  const baseId = useId();
+  const maskId1 = `${baseId}-mask1`;
+  const maskId2 = `${baseId}-mask2`;
+  const maskId3 = `${baseId}-mask3`;

82-116: Consider extracting mask configuration to improve maintainability.

The hardcoded mask configuration could be extracted to a constant for better maintainability and clarity.

const MASK_CONFIGS = [
  { r: 225, maskStart: 10, maskEnd: 90 },
  { r: 162.5, maskStart: 15, maskEnd: 85 },
  { r: 100, maskStart: 20, maskEnd: 80 },
] as const;

Then reference it in both the gradient generation (lines 82-116) and mask generation (lines 118-133).

packages/react/src/isomorphicClerk.ts (1)

737-741: Add explicit return type for public API.

Type the field to satisfy “explicit return types” guideline and avoid inference drift.

-  __experimental_checkout = (...args: Parameters<Clerk['__experimental_checkout']>) => {
+  __experimental_checkout: (
+    ...args: Parameters<Clerk['__experimental_checkout']>
+  ) => ReturnType<Clerk['__experimental_checkout']> = (...args) => {
     return this.loaded && this.clerkjs
       ? this.clerkjs.__experimental_checkout(...args)
       : this.#stateProxy.checkoutSignal(...args);
   };
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)

143-145: Clarify error message for wrong status.

Make it actionable; this isn’t “not found”.

-  if (checkout.status !== 'needs_confirmation') {
-    throw new Error('Checkout not found');
-  }
+  if (checkout.status !== 'needs_confirmation') {
+    throw new Error('Clerk: Checkout is not ready to confirm.');
+  }

151-158: Remove @ts-expect-error; handle unknown cleanly and ensure cleanup.

sdk returns { error: unknown }. handleError accepts unknown. Also wrap in try/finally to always clear loading.

-    const { error } = await checkout.confirm(params);
-
-    if (error) {
-      // @ts-expect-error - error is not an Error
-      handleError(error, [], card.setError);
-    } else {
-      onSubscriptionComplete?.();
-    }
-    card.setIdle();
+    try {
+      const { error } = await checkout.confirm(params);
+      if (error) {
+        handleError(error, [], card.setError);
+      } else {
+        onSubscriptionComplete?.();
+      }
+    } finally {
+      card.setIdle();
+    }
packages/shared/src/react/hooks/useCheckout.ts (1)

40-52: Stabilize subscription deps.

Depending on deep properties can be brittle; depend on clerk object instead.

-    [signal, clerk.loaded, clerk.__internal_state],
+    [signal, clerk],
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

12-15: Tighten planPeriod type.

Use CommerceSubscriptionPlanPeriod instead of string.

-function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey {
+function cacheKey(options: {
+  userId: string;
+  orgId?: string;
+  planId: string;
+  planPeriod: import('@clerk/types').CommerceSubscriptionPlanPeriod;
+}): CheckoutKey {
packages/types/src/clerk.ts (1)

62-71: Consider exporting GlobalErrors for consumers.

Public types reference GlobalErrors; exporting helps downstream typing.

-interface GlobalErrors {
+export interface GlobalErrors {
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)

60-76: Correct retry comment to match config.

Current settings cap delays at ~2s; comment says 2s/4s/6s/8s.

-    // Retry confirmation in case of a 500 error
-    // This will retry up to 3 times with an increasing delay
-    // It retries at 2s, 4s, 6s and 8s
+    // Retry confirmation on transient errors.
+    // Up to 4 attempts with ~2s delays (bounded by maxDelayBetweenRetries).

148-149: Tighten pendingOperations type.

Map never stores null; drop union.

-  private readonly pendingOperations = new Map<string, Promise<{ error: unknown }> | null>();
+  private readonly pendingOperations = new Map<string, Promise<{ error: unknown }>>();
📜 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 b26c58f and a95b63a.

📒 Files selected for processing (14)
  • packages/clerk-js/src/core/clerk.ts (2 hunks)
  • packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts (0 hunks)
  • packages/clerk-js/src/core/modules/checkout/instance.ts (3 hunks)
  • packages/clerk-js/src/core/modules/checkout/manager.ts (0 hunks)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (4 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx (3 hunks)
  • packages/react/src/isomorphicClerk.ts (1 hunks)
  • packages/react/src/stateProxy.ts (5 hunks)
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts (1 hunks)
  • packages/shared/src/react/hooks/useCheckout.ts (1 hunks)
  • packages/types/src/clerk.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/clerk-js/src/core/modules/checkout/tests/manager.spec.ts
  • packages/clerk-js/src/core/modules/checkout/manager.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/react/src/stateProxy.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/clerk-js/src/core/clerk.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{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/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/core/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/core/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/core/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/core/modules/checkout/instance.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/types/src/clerk.ts
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
**/__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/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
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/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/Checkout/CheckoutComplete.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (6)
packages/clerk-js/src/core/modules/checkout/instance.ts (2)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • CheckoutFuture (144-218)
  • createSignals (127-142)
packages/types/src/clerk.ts (2)
  • __experimental_CheckoutOptions (99-103)
  • NullableCheckoutSignal (91-93)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
  • FieldError (7-20)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResource (1412-1427)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)
packages/clerk-js/src/ui/customizables/index.ts (1)
  • Box (15-15)
packages/clerk-js/src/ui/customizables/elementDescriptors.ts (1)
  • descriptors (568-568)
packages/shared/src/react/hooks/useCheckout.ts (2)
packages/types/src/clerk.ts (1)
  • CheckoutSignalValue (76-89)
packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
  • useCheckoutContext (10-37)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (76-89)
  • CheckoutSignal (95-97)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
packages/react/src/stateProxy.ts (1)
  • checkout (188-194)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (6)
packages/react/src/stateProxy.ts (1)
  • checkout (188-194)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • plan (168-170)
  • totals (174-176)
packages/types/src/commerce.ts (1)
  • ConfirmCheckoutParams (1162-1188)
packages/clerk-js/src/ui/utils/errorHandler.ts (1)
  • handleError (69-91)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • usePaymentMethods (32-40)
packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
  • usePaymentMethods (9-21)
⏰ 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). (6)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)

17-157: LGTM! Clean component extraction with proper encapsulation.

The SuccessRing component is well-implemented with proper state management, unique ID generation for SVG elements, and clean separation of concerns. The animation logic is correctly encapsulated within the component.


252-255: LGTM! Clean component integration.

The integration of SuccessRing maintains the existing API while improving code organization and reusability.

packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)

197-199: LGTM: totals readiness guard.

This prevents early render crashes.

packages/shared/src/react/hooks/useCheckout.ts (1)

57-60: Verify non-null checkout invariant.

Hook type says CheckoutSignalValue (non-null checkout), but upstream can be nullable early. Confirm the invariant holds or relax the return type to NullableCheckoutSignal.

Would you like me to scan the codebase to ensure __experimental_checkout never yields a null checkout at the time useCheckout reads it?

type UseCheckoutReturn = ReturnType<typeof useCheckout>;

describe('useCheckout type tests', () => {
describe.skip('useCheckout type tests', () => {
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

Don’t skip; update tests to new signal API.

The suite still references removed types (__experimental_CheckoutInstance, __experimental_CheckoutCacheState) and wrong unions (fetchStatus includes 'error'). Align with CheckoutSignalValue/CheckoutFutureResource and unskip.

-import type {
-  ClerkAPIResponseError,
-  CommerceCheckoutResource,
-  CommerceSubscriptionPlanPeriod,
-  ConfirmCheckoutParams,
-  SetActiveNavigate,
-} from '@clerk/types';
+import type {
+  ClerkAPIResponseError,
+  CommerceCheckoutResource,
+  CommerceSubscriptionPlanPeriod,
+  ConfirmCheckoutParams,
+  SetActiveNavigate,
+  CheckoutSignalValue,
+  CheckoutFutureResource,
+} from '@clerk/types';
@@
-describe.skip('useCheckout type tests', () => {
+describe('useCheckout type tests', () => {
@@
-    type CheckoutObject = UseCheckoutReturn['checkout'];
+    type CheckoutObject = CheckoutSignalValue['checkout'];
@@
-        type Methods = Pick<CheckoutObject, 'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>;
+        type Methods = Pick<CheckoutObject, 'confirm' | 'start'>;
@@
-        expectTypeOf<MethodNames>().toEqualTypeOf<'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>();
+        expectTypeOf<MethodNames>().toEqualTypeOf<'confirm' | 'start'>();
@@
-        type Methods = Pick<CheckoutObject, 'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>;
+        type Methods = Pick<CheckoutObject, 'confirm' | 'start'>;
@@
-        type ClearMethod = Methods['clear'];
-        type FinalizeMethod = Methods['finalize'];
-        type GetStateMethod = Methods['getState'];
+        // removed in signal-based API
@@
-        expectTypeOf<ClearMethod>().returns.toBeVoid();
-        expectTypeOf<FinalizeMethod>().parameter(0).toEqualTypeOf<{ navigate?: SetActiveNavigate } | undefined>();
-        expectTypeOf<GetStateMethod>().returns.toEqualTypeOf<__experimental_CheckoutCacheState>();
+        // n/a in v2
@@
-        type StatusProps = Pick<CheckoutObject, 'isStarting' | 'isConfirming' | 'error' | 'status' | 'fetchStatus'>;
+        type StatusProps = Pick<CheckoutObject, 'status'> & { fetchStatus: CheckoutSignalValue['fetchStatus'] };
@@
-        expectTypeOf<FetchStatus>().toEqualTypeOf<'idle' | 'fetching' | 'error'>();
+        expectTypeOf<FetchStatus>().toEqualTypeOf<'idle' | 'fetching'>();
@@
-          expectTypeOf<CheckoutObject['confirm']>().toEqualTypeOf<__experimental_CheckoutInstance['confirm']>();
-          expectTypeOf<CheckoutObject['start']>().toEqualTypeOf<__experimental_CheckoutInstance['start']>();
-          expectTypeOf<CheckoutObject['clear']>().toEqualTypeOf<() => void>();
-          expectTypeOf<CheckoutObject['finalize']>().toEqualTypeOf<
-            (params?: { navigate?: SetActiveNavigate }) => void
-          >();
-          expectTypeOf<CheckoutObject['getState']>().toEqualTypeOf<() => __experimental_CheckoutCacheState>();
+          expectTypeOf<CheckoutObject['confirm']>().parameter(0).toEqualTypeOf<ConfirmCheckoutParams>();
+          expectTypeOf<CheckoutObject['start']>().parameters.toEqualTypeOf<[]>();
📝 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
describe.skip('useCheckout type tests', () => {
describe('useCheckout type tests', () => {

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

Caution

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

⚠️ Outside diff range comments (3)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)

148-166: Make confirm flow exception-safe; always clear loading.

A thrown error (unknown from confirm/handleError) leaves the card in loading state and skips error handling. Use try/catch/finally and improve the thrown message.

-  if (checkout.status !== 'needs_confirmation') {
-    throw new Error('Checkout not found');
-  }
+  if (checkout.status !== 'needs_confirmation') {
+    throw new Error(`Checkout not ready (status: ${checkout.status})`);
+  }
 
   const confirmCheckout = async (params: ConfirmCheckoutParams) => {
-    card.setLoading();
-    card.setError(undefined);
-
-    const { error } = await checkout.confirm(params);
-
-    if (error) {
-      // @ts-expect-error - error is not an Error
-      handleError(error, [], card.setError);
-    } else {
-      onSubscriptionComplete?.();
-    }
-    card.setIdle();
+    card.setLoading();
+    card.setError(undefined);
+    try {
+      const { error } = await checkout.confirm(params);
+      if (error) {
+        handleError(error as unknown, [], card.setError);
+        return;
+      }
+      onSubscriptionComplete?.();
+    } catch (err) {
+      handleError(err, [], card.setError);
+    } finally {
+      card.setIdle();
+    }
   };

215-219: Localize the aria-label.

Hard-coded string; use useLocalizations per guidelines.

+  const { t } = useLocalizations();
...
-        <SegmentedControl.Root
-          aria-label='Payment method source'
+        <SegmentedControl.Root
+          aria-label={t(localizationKeys('commerce.checkout.paymentMethodSource'))}
packages/react/src/stateProxy.ts (1)

198-206: Harden gateProperty for null targets.

Prevents crashes when the signal’s checkout is still null even after Clerk is loaded.

-  private gateProperty<T extends object, K extends keyof T>(getTarget: () => T, key: K, defaultValue: T[K]) {
-    return (() => {
-      if (!inBrowser() || !this.isomorphicClerk.loaded) {
-        return defaultValue;
-      }
-      const t = getTarget();
-      return t[key];
-    })();
-  }
+  private gateProperty<T extends object, K extends keyof T>(
+    getTarget: () => T | null,
+    key: K,
+    defaultValue: T[K],
+  ) {
+    return (() => {
+      if (!inBrowser() || !this.isomorphicClerk.loaded) {
+        return defaultValue;
+      }
+      const t = getTarget() as any;
+      const v = t?.[key];
+      return typeof v === 'undefined' ? defaultValue : (v as T[K]);
+    })();
+  }
♻️ Duplicate comments (4)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)

34-41: Guard totals to avoid null deref (repeat).

totals is used below without a null check; add it to the early return guard.

-  if (!plan) {
+  if (!plan || !totals) {
     return null;
   }
packages/types/src/clerk.ts (1)

73-89: Fix copy/paste JSDoc: replace SignIn references with Checkout (repeat).

Docstrings still reference SignIn; update to Checkout to match the API.

-/**
- * The value returned by the `useSignInSignal` hook.
- */
+/**
+ * The value returned by the Checkout signal.
+ */
 export interface CheckoutSignalValue {
   /**
-   * Represents the errors that occurred during the last fetch of the parent resource.
+   * Represents the errors that occurred during the last fetch of the Checkout resource.
    */
   errors: GlobalErrors;
   /**
-   * The fetch status of the underlying `SignIn` resource.
+   * The fetch status of the underlying Checkout resource.
    */
   fetchStatus: 'idle' | 'fetching';
   /**
-   * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows.
+   * An instance representing the currently active Checkout, with APIs designed for custom flows.
    */
   checkout: CheckoutFutureResource;
 }
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)

128-142: Type the computed signal explicitly and drop redundant property shorthand

Add the proper generic to computed and use property shorthand. This also mirrors previous feedback.

-import type {
+import type {
   CheckoutFutureResourceLax,
   CheckoutSignal,
   CheckoutSignalValue,
+  NullableCheckoutSignalValue,
@@
-export const createSignals = () => {
+export const createSignals = () => {
@@
-  const computedSignal: CheckoutSignal = computed(() => {
+  const computedSignal: CheckoutSignal = computed<NullableCheckoutSignalValue>(() => {
@@
-    const errors = errorsToParsedErrors(error);
-    return { errors: errors, fetchStatus, checkout: resource };
+    const errors = errorsToParsedErrors(error);
+    return { errors, fetchStatus, checkout: resource };
   });

195-207: Guard against missing billing client and falsy checkout (can set this.resource to undefined)

If clerk.billing is absent or returns a falsy value, this.resource becomes invalid. Add a precondition and validate the result.

   async start(): Promise<{ error: unknown }> {
     return this.runAsyncResourceTask(
       'start',
       async () => {
-        const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout;
-        this.resource = checkout;
+        const billing = CommerceCheckout.clerk.billing;
+        if (!billing?.startCheckout) {
+          throw new Error('Checkout cannot start: billing client is not available.');
+        }
+        const checkout = await billing.startCheckout(this.config);
+        if (!checkout) {
+          throw new Error('Checkout could not be initialized.');
+        }
+        this.resource = checkout as CommerceCheckout;
       },
       () => {
         this.resource = new CommerceCheckout(null);
         this.signals.resourceSignal({ resource: this });
       },
     );
   }
🧹 Nitpick comments (9)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)

159-161: Remove @ts-expect-error; cast to unknown for handleError.

Avoid suppression; handleError is designed for unknown errors.

-      // @ts-expect-error - error is not an Error
-      handleError(error, [], card.setError);
+      handleError(error as unknown, [], card.setError);

396-401: Avoid controlled/uncontrolled warnings on hidden input.

Ensure the value is always a string.

-              value={selectedPaymentSource?.id}
+              value={selectedPaymentSource?.id ?? ''}

And similarly for the hidden input at Lines 418–423.

Also applies to: 418-423

packages/react/src/stateProxy.ts (1)

208-220: Gracefully guard gateMethod against null targets.

Avoids .apply on undefined and surfaces a clearer error.

-      const t = getTarget();
-      return (t[key] as (...args: Parameters<F>) => ReturnType<F>).apply(t, args);
+      const t = getTarget() as any;
+      if (!t || typeof t[key] !== 'function') {
+        return errorThrower.throw(`Attempted to call (${key}) before checkout is ready.`);
+      }
+      return (t[key] as (...args: Parameters<F>) => ReturnType<F>).apply(t, args);
packages/types/src/commerce.ts (1)

1356-1446: Type surface for CheckoutFuture looks solid; minor nit on planPeriodStart docs.

planPeriodStart is number | undefined in confirmed states and null in initialization via ForceNull. Consider noting “number (ms) | undefined | null” in the JSDoc for clarity.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)

61-92: Add JSDoc to public method confirm

Public APIs need JSDoc per guidelines. Briefly document behavior and retry conditions.

+  /**
+   * Confirms the checkout. Retries on 5xx and `checkout_already_in_progress` (409) with bounded backoff.
+   * @param params Checkout confirmation parameters
+   * @returns The updated checkout instance
+   */
   confirm = (params: ConfirmCheckoutParams): Promise<this> => {

95-126: Tighten error parsing; avoid shadowing and simplify pushes

Avoid parameter shadowing and use ??= to trim branches. If global is intended for developer-facing messages, consider mapping to e.message.

-function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
+function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
@@
-  if (!isClerkAPIResponseError(error)) {
+  if (!isClerkAPIResponseError(error)) {
     parsedErrors.raw = [];
     parsedErrors.global = [];
     return parsedErrors;
   }
 
-  error.errors.forEach(error => {
-    if (parsedErrors.raw) {
-      parsedErrors.raw.push(error);
-    } else {
-      parsedErrors.raw = [error];
-    }
-
-    if (parsedErrors.global) {
-      parsedErrors.global.push(error);
-    } else {
-      parsedErrors.global = [error];
-    }
-  });
+  for (const e of error.errors) {
+    parsedErrors.raw ??= [];
+    parsedErrors.raw.push(e);
+    parsedErrors.global ??= [];
+    parsedErrors.global.push(e);
+    // If GlobalErrors.global should be strings, use:
+    // parsedErrors.global.push(e.long_message ?? e.message ?? e.code);
+  }
 
   return parsedErrors;
 }

128-142: Add JSDoc to createSignals (public API)

Expose intent and shapes for consumers.

+/**
+ * Creates reactive signals for the checkout flow.
+ * - resourceSignal: holds the current CheckoutFuture resource
+ * - errorSignal: holds the last operation error
+ * - fetchSignal: indicates 'idle' | 'fetching'
+ * - computedSignal: CheckoutSignal combining the above for consumers
+ */
 export const createSignals = () => {

144-154: Add JSDoc to CheckoutFuture (public API)

Per guidelines, document lifecycle and concurrency behavior.

+/**
+ * Public facade for the signal-driven checkout lifecycle.
+ * Ensures per-operation concurrency control and emits batched updates to signals.
+ */
 export class CheckoutFuture implements CheckoutFutureResourceLax {

61-91: Align retry comment and config in CommerceCheckout.confirm

  • The comment's delay sequence is wrong: with initialDelay=2000, factor=1.1 and maxDelayBetweenRetries=2000 the retries will be ~2s (capped), not 2s/4s/6s/8s.
  • The "retry up to 3 times" text is consistent with the shouldRetry check (iterations >= 4 allows 3 retries after the initial attempt).

Suggested minimal change (update comment to match actual behavior):

-    // Retry confirmation in case of a 500 error
-    // This will retry up to 3 times with an increasing delay
-    // It retries at 2s, 4s, 6s and 8s
+    // Retry confirmation in case of a 500 error
+    // Attempts up to 4 times total (initial + up to 3 retries). Delays start at 2s
+    // and are capped at 2s by maxDelayBetweenRetries (so retries will be ~2s apart).

Optional alternative if you want growing delays: set maxDelayBetweenRetries: 8 * 1000 and choose an appropriate factor (e.g., factor: 2 for 2s→4s→8s). Linear increments of 2s (2s,4s,6s,8s) are not supported by retry() without changing its implementation.

📜 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 a95b63a and 813edcc.

📒 Files selected for processing (7)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (5 hunks)
  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (1 hunks)
  • packages/react/src/stateProxy.ts (5 hunks)
  • packages/types/src/clerk.ts (2 hunks)
  • packages/types/src/commerce.ts (2 hunks)
  • packages/types/src/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/types/src/utils.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/PaymentSources/PaymentSourceRow.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.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/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.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/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.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/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.ts
**/*.{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/PaymentSources/PaymentSourceRow.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.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/PaymentSources/PaymentSourceRow.tsx
  • packages/types/src/commerce.ts
  • packages/types/src/clerk.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/react/src/stateProxy.ts
**/*.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/PaymentSources/PaymentSourceRow.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (6)
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (2)
packages/types/src/utils.ts (1)
  • RemoveFunctions (127-129)
packages/types/src/commerce.ts (1)
  • CommercePaymentSourceResource (487-534)
packages/types/src/commerce.ts (1)
packages/types/src/utils.ts (3)
  • Prettify (131-133)
  • RemoveFunctions (127-129)
  • ForceNull (47-49)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
  • FieldError (7-20)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResource (1412-1427)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (8)
packages/react/src/stateProxy.ts (1)
  • checkout (190-196)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
  • plan (168-170)
  • totals (174-176)
  • paymentSource (187-189)
packages/types/src/commerce.ts (2)
  • ConfirmCheckoutParams (1162-1188)
  • CommercePaymentSourceResource (487-534)
packages/clerk-js/src/ui/utils/errorHandler.ts (1)
  • handleError (69-91)
packages/shared/src/react/hooks/index.ts (1)
  • usePaymentMethods (13-13)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • usePaymentMethods (32-40)
packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
  • usePaymentMethods (9-21)
packages/types/src/utils.ts (1)
  • RemoveFunctions (127-129)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (76-89)
  • CheckoutSignal (95-97)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
packages/react/src/stateProxy.ts (3)
packages/types/src/commerce.ts (3)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
  • CheckoutFutureResource (1412-1427)
packages/types/src/clerk.ts (2)
  • CheckoutSignalValue (76-89)
  • Clerk (169-892)
packages/clerk-js/src/core/clerk.ts (1)
  • Clerk (200-2948)
⏰ 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 (8)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)

373-384: Verify intent: shouldDefaultBeUsed condition appears inverted.

If “default should be used,” why render the picker when true? Please confirm or rename to reflect behavior.

Would you like me to flip the condition and render the hidden input when shouldDefaultBeUsed is true?


355-358: Type narrowing for selectedPaymentSource looks good.

Using RemoveFunctions<CommercePaymentSourceResource> avoids leaking methods into display-only components.

packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (1)

1-10: Prop type change to RemoveFunctions is appropriate.

Aligns with display-only usage; no runtime changes.

packages/react/src/stateProxy.ts (1)

32-36: New checkout signal surface: API shape looks consistent.

Types and parameters align with the new CheckoutFutureResource.

Also applies to: 51-53

packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)

36-39: Constructor default to null looks good

The nullable JSON input with a sane default enables a clean “uninitialized” state. No issues here.


209-216: Good guard before confirm

Prevents invalid API paths by enforcing initialization first. Looks correct.


223-259: Balanced batching and per-operation deduping look solid

startBatch/endBatch are balanced, and pendingOperations ensures coalesced concurrent calls. Good structure.


156-193: Getters return null pre-init (not undefined) — adjust types or add guards

Types show CheckoutFutureResource uses ForceNull for status === 'needs_initialization', so these getters can return null (not undefined). Either annotate the getters with nullable return types (e.g. Foo | null) or throw/guard when status === 'needs_initialization' if non-null values are required.

Likely an incorrect or invalid review comment.

Comment on lines +195 to 205
const { totals, freeTrialEndsAt } = checkout;
const { data: paymentSources } = usePaymentMethods();

const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() =>
paymentSources.length > 0 ? 'existing' : 'new',
);

if (!id) {
if (!totals) {
return 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

Default paymentSources to an empty array.

Prevents runtime errors when data is initially undefined.

-  const { data: paymentSources } = usePaymentMethods();
+  const { data: paymentSources = [] } = usePaymentMethods();
📝 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 { totals, freeTrialEndsAt } = checkout;
const { data: paymentSources } = usePaymentMethods();
const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() =>
paymentSources.length > 0 ? 'existing' : 'new',
);
if (!id) {
if (!totals) {
return null;
}
const { totals, freeTrialEndsAt } = checkout;
const { data: paymentSources = [] } = usePaymentMethods();
const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() =>
paymentSources.length > 0 ? 'existing' : 'new',
);
if (!totals) {
return null;
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines
195 to 205, paymentSources can be undefined because usePaymentMethods().data may
be initially undefined; default paymentSources to an empty array when
destructuring so the subsequent state initializer (and any array operations)
don't throw. Update the destructuring to provide a default empty array for data
(paymentSources) and ensure the useState initializer continues to handle an
empty array correctly.

Comment on lines +123 to +174
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue {
const gateProperty = this.gateProperty.bind(this);
const targetCheckout = () => this.checkout(params);
const target = () => targetCheckout().checkout as CheckoutFutureResource;

return {
errors: {
raw: null,
global: null,
},
fetchStatus: 'idle' as const,
checkout: {
get status() {
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
},
get externalClientSecret() {
return gateProperty(target, 'externalClientSecret', null) as null;
},
get externalGatewayId() {
return gateProperty(target, 'externalGatewayId', null) as null;
},
get paymentSource() {
return gateProperty(target, 'paymentSource', null) as null;
},
get plan() {
return gateProperty(target, 'plan', null) as null;
},
get planPeriod() {
return gateProperty(target, 'planPeriod', null) as null;
},
get totals() {
return gateProperty(target, 'totals', null) as null;
},
get isImmediatePlanChange() {
return gateProperty(target, 'isImmediatePlanChange', false) as null;
},
get freeTrialEndsAt() {
return gateProperty(target, 'freeTrialEndsAt', null) as null;
},
get payer() {
return gateProperty(target, 'payer', null) as null;
},
get planPeriodStart() {
return gateProperty(target, 'planPeriodStart', null) as null;
},

start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'),
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'),
},
};
}
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

Fix nullability and default-value mismatches in checkout proxy.

  • targetCheckout().checkout can be null; current cast risks crashes.
  • Several getters return runtime booleans while typed as null via as null.
-    const targetCheckout = () => this.checkout(params);
-    const target = () => targetCheckout().checkout as CheckoutFutureResource;
+    const targetCheckout = () => this.checkout(params);
+    const target = () => targetCheckout().checkout;
...
-        get status() {
-          return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
+        get status() {
+          return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
         },
         get externalClientSecret() {
-          return gateProperty(target, 'externalClientSecret', null) as null;
+          return gateProperty(target, 'externalClientSecret', null);
         },
         get externalGatewayId() {
-          return gateProperty(target, 'externalGatewayId', null) as null;
+          return gateProperty(target, 'externalGatewayId', null);
         },
         get paymentSource() {
-          return gateProperty(target, 'paymentSource', null) as null;
+          return gateProperty(target, 'paymentSource', null);
         },
         get plan() {
-          return gateProperty(target, 'plan', null) as null;
+          return gateProperty(target, 'plan', null);
         },
         get planPeriod() {
-          return gateProperty(target, 'planPeriod', null) as null;
+          return gateProperty(target, 'planPeriod', null);
         },
         get totals() {
-          return gateProperty(target, 'totals', null) as null;
+          return gateProperty(target, 'totals', null);
         },
         get isImmediatePlanChange() {
-          return gateProperty(target, 'isImmediatePlanChange', false) as null;
+          return gateProperty(target, 'isImmediatePlanChange', null);
         },
         get freeTrialEndsAt() {
-          return gateProperty(target, 'freeTrialEndsAt', null) as null;
+          return gateProperty(target, 'freeTrialEndsAt', null);
         },
         get payer() {
-          return gateProperty(target, 'payer', null) as null;
+          return gateProperty(target, 'payer', null);
         },
         get planPeriodStart() {
-          return gateProperty(target, 'planPeriodStart', null) as null;
+          return gateProperty(target, 'planPeriodStart', null);
         },

Additionally, update gateProperty to handle null targets and undefined values safely (see next comment).

📝 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
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue {
const gateProperty = this.gateProperty.bind(this);
const targetCheckout = () => this.checkout(params);
const target = () => targetCheckout().checkout as CheckoutFutureResource;
return {
errors: {
raw: null,
global: null,
},
fetchStatus: 'idle' as const,
checkout: {
get status() {
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
},
get externalClientSecret() {
return gateProperty(target, 'externalClientSecret', null) as null;
},
get externalGatewayId() {
return gateProperty(target, 'externalGatewayId', null) as null;
},
get paymentSource() {
return gateProperty(target, 'paymentSource', null) as null;
},
get plan() {
return gateProperty(target, 'plan', null) as null;
},
get planPeriod() {
return gateProperty(target, 'planPeriod', null) as null;
},
get totals() {
return gateProperty(target, 'totals', null) as null;
},
get isImmediatePlanChange() {
return gateProperty(target, 'isImmediatePlanChange', false) as null;
},
get freeTrialEndsAt() {
return gateProperty(target, 'freeTrialEndsAt', null) as null;
},
get payer() {
return gateProperty(target, 'payer', null) as null;
},
get planPeriodStart() {
return gateProperty(target, 'planPeriodStart', null) as null;
},
start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'),
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'),
},
};
}
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue {
const gateProperty = this.gateProperty.bind(this);
const targetCheckout = () => this.checkout(params);
const target = () => targetCheckout().checkout;
return {
errors: {
raw: null,
global: null,
},
fetchStatus: 'idle' as const,
checkout: {
get status() {
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
},
get externalClientSecret() {
return gateProperty(target, 'externalClientSecret', null);
},
get externalGatewayId() {
return gateProperty(target, 'externalGatewayId', null);
},
get paymentSource() {
return gateProperty(target, 'paymentSource', null);
},
get plan() {
return gateProperty(target, 'plan', null);
},
get planPeriod() {
return gateProperty(target, 'planPeriod', null);
},
get totals() {
return gateProperty(target, 'totals', null);
},
get isImmediatePlanChange() {
return gateProperty(target, 'isImmediatePlanChange', null);
},
get freeTrialEndsAt() {
return gateProperty(target, 'freeTrialEndsAt', null);
},
get payer() {
return gateProperty(target, 'payer', null);
},
get planPeriodStart() {
return gateProperty(target, 'planPeriodStart', null);
},
start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'),
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'),
},
};
}
🤖 Prompt for AI Agents
In packages/react/src/stateProxy.ts around lines 123 to 173, the checkout proxy
assumes targetCheckout().checkout is always non-null and several getters use
incorrect null-typed defaults (e.g. booleans typed as null); update the proxy so
target is typed as CheckoutFutureResource | null (don’t force-cast), have
gateProperty calls use appropriate default values and types (use false for
boolean fields like isImmediatePlanChange, keep null only for genuinely nullable
objects like
externalClientSecret/externalGatewayId/paymentSource/plan/planPeriod/totals/freeTrialEndsAt/payer/planPeriodStart),
and replace the current target() => targetCheckout().checkout with a null-safe
accessor that returns null when checkout is absent; also update gateProperty (as
noted in the other comment) to gracefully handle a null target and undefined
property values so the getters never throw when checkout is null or properties
are undefined.

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

♻️ Duplicate comments (3)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

17-18: Cache survives user/org switches — scope per Clerk instance or add invalidation

Module-level Map will leak stale entries across sign-out/sign-in and org changes. Move cache to a WeakMap keyed by Clerk or expose a clear function and call it on signOut and organization changes.

Apply:

-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
+const instanceCache = new WeakMap<
+  Clerk,
+  Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>
+>();
+
+function getCache(clerk: Clerk) {
+  let m = instanceCache.get(clerk);
+  if (!m) {
+    m = new Map();
+    instanceCache.set(clerk, m);
+  }
+  return m;
+}
+
+export function clearCheckoutCache(clerk: Clerk) {
+  instanceCache.get(clerk)?.clear();
+}

And inside createCheckoutInstance use const cache = getCache(clerk);.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)

197-209: Guard billing availability and falsy checkout to prevent undefined assignment

billing?.startCheckout may be undefined and could yield undefined assigned to this.resource. Throw a clear, actionable error instead.

-      async () => {
-        const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout;
-        this.resource = checkout;
-      },
+      async () => {
+        const billing = CommerceCheckout.clerk.billing;
+        if (!billing?.startCheckout) {
+          throw new Error('Clerk: Billing client unavailable. Ensure billing is enabled and ClerkProvider is configured.');
+        }
+        const checkout = await billing.startCheckout(this.config);
+        if (!checkout) {
+          throw new Error('Clerk: Failed to initialize checkout.');
+        }
+        this.resource = checkout as CommerceCheckout;
+      },
packages/types/src/clerk.ts (1)

73-89: Fix copy/paste JSDoc: replace SignIn with Checkout

Update comments to reflect Checkout.

-/**
- * The value returned by the `useSignInSignal` hook.
- */
+/**
+ * The value returned by the Checkout signal.
+ */
@@
-  /**
-   * Represents the errors that occurred during the last fetch of the parent resource.
-   */
+  /** Represents the errors that occurred during the last fetch of the Checkout resource. */
@@
-  /**
-   * The fetch status of the underlying `SignIn` resource.
-   */
+  /** The fetch status of the underlying Checkout resource. */
@@
-  /**
-   * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows.
-   */
+  /** An instance representing the currently active Checkout, with APIs designed for custom flows. */
🧹 Nitpick comments (8)
packages/types/src/utils.ts (2)

44-50: Confirm intended nullability semantics for ForceNull

Current mapping preserves optional and readonly modifiers. If you meant “every key present and set to null,” make properties required.

Example tweak:

-export type ForceNull<T> = {
-  [K in keyof T]: null;
-};
+export type ForceNull<T> = {
+  [K in keyof T]-?: null;
+};

124-134: Tighten RemoveFunctions (avoid any; include constructors) and document Prettify

  • Replace any with unknown to meet typing guidelines.
  • Optionally exclude constructor properties as well.
  • Add JSDoc for Prettify (public API).
-/**
- * Utility type that removes function properties from a type.
- */
-export type RemoveFunctions<T extends object> = {
-  [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K];
-};
+/**
+ * Utility type that removes callable properties (methods) from a type.
+ * Note: shallow — does not traverse nested objects.
+ */
+export type RemoveFunctions<T extends object> = {
+  [K in keyof T as T[K] extends ((...args: unknown[]) => unknown)
+    | (abstract new (...args: unknown[]) => unknown) ? never : K]: T[K];
+};
 
-export type Prettify<T> = {
-  [K in keyof T]: T[K];
-} & {};
+/**
+ * Normalizes/expands mapped or intersection types for nicer editor display; no semantic change.
+ * @public
+ */
+export type Prettify<T> = { [K in keyof T]: T[K] } & {};

If you also want a deep variant, I can add DeepRemoveFunctions separately.

packages/clerk-js/src/core/modules/checkout/instance.ts (2)

12-15: Tighten types and avoid delimiter collisions in cache key

Use the domain type for planPeriod and a collision-safe key.

-import type { __experimental_CheckoutOptions, CheckoutSignalValue } from '@clerk/types';
+import type { __experimental_CheckoutOptions, CheckoutSignalValue, CommerceSubscriptionPlanPeriod } from '@clerk/types';
@@
-function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey {
-  const { userId, orgId, planId, planPeriod } = options;
-  return `${userId}-${orgId || 'user'}-${planId}-${planPeriod}` as CheckoutKey;
+function cacheKey(options: {
+  userId: string;
+  orgId?: string;
+  planId: string;
+  planPeriod: CommerceSubscriptionPlanPeriod;
+}): CheckoutKey {
+  const { userId, orgId, planId, planPeriod } = options;
+  return JSON.stringify([userId, orgId ?? 'user', planId, planPeriod]) as CheckoutKey;
 }

40-44: Avoid unsafe cast; align computed type upstream

computedSignal() returns a shape with checkout | null, but you cast to non-null CheckoutSignalValue. Either (a) type computedSignal as CheckoutSignalValue and ensure non-null before exposure, or (b) allow checkout | null in the public type. Recommend (a); see suggested change in CommerceCheckout.ts.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)

61-92: Retry comment doesn’t match config

Comment says retries at 2s, 4s, 6s, 8s, but factor: 1.1 with maxDelayBetweenRetries: 2000 won’t produce those delays. Update the comment or config.


128-141: Type computed as the public shape and use property shorthand

Eliminates casts downstream and minor style nit.

-  const computedSignal = computed<Omit<CheckoutSignalValue, 'checkout'> & { checkout: CheckoutFutureResource | null }>(
-    () => {
-      const resource = resourceSignal().resource;
-      const error = errorSignal().error;
-      const fetchStatus = fetchSignal().status;
-
-      const errors = errorsToParsedErrors(error);
-      return { errors: errors, fetchStatus, checkout: resource };
-    },
-  );
+  const computedSignal = computed<CheckoutSignalValue>(() => {
+    const resource = resourceSignal().resource;
+    const error = errorSignal().error;
+    const fetchStatus = fetchSignal().status;
+    const errors = errorsToParsedErrors(error);
+    // `resource` is set by CheckoutFuture ctor before exposure.
+    return { errors, fetchStatus, checkout: resource as CheckoutFutureResource };
+  });

95-126: Global errors parsing is overly naive

You push all API errors into global, which contradicts the doc “does not include any errors that could be parsed as a field error.” If field-level data is available (e.g., path/param), split accordingly; otherwise, consider keeping global: null when field scoping is unknown.

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

91-93: Dead/ambiguous type: remove or document CheckoutSignal

Either use this alias for callable signals or remove it to avoid confusion now that the API returns a value.

📜 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 813edcc and 6be9212.

📒 Files selected for processing (9)
  • packages/clerk-js/src/core/clerk.ts (2 hunks)
  • packages/clerk-js/src/core/modules/checkout/instance.ts (3 hunks)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (5 hunks)
  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (1 hunks)
  • packages/react/src/stateProxy.ts (5 hunks)
  • packages/types/src/clerk.ts (2 hunks)
  • packages/types/src/commerce.ts (2 hunks)
  • packages/types/src/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/react/src/stateProxy.ts
  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/clerk-js/src/core/clerk.ts
  • packages/types/src/commerce.ts
  • 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/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/resources/CommerceCheckout.ts
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.ts
🧬 Code graph analysis (3)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
  • CommerceCheckoutJSON (880-895)
packages/types/src/clerk.ts (1)
  • CheckoutSignalValue (76-89)
packages/types/src/commerce.ts (4)
  • CheckoutFutureResource (1412-1427)
  • CheckoutFutureResourceLax (1429-1446)
  • CreateCheckoutParams (1136-1155)
  • ConfirmCheckoutParams (1162-1188)
packages/react/src/stateProxy.ts (1)
  • checkout (190-196)
packages/clerk-js/src/core/modules/checkout/instance.ts (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • CheckoutFuture (146-223)
  • createSignals (128-144)
packages/clerk-js/src/core/clerk.ts (1)
  • Clerk (200-2948)
packages/types/src/clerk.ts (3)
  • Clerk (165-888)
  • __experimental_CheckoutOptions (95-99)
  • CheckoutSignalValue (76-89)
packages/react/src/stateProxy.ts (1)
  • checkout (190-196)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
  • FieldError (7-20)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResource (1412-1427)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
⏰ 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). (6)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

22-55: Resolved — returning snapshot is correct

Checked usages: callers invoke clerk.__experimental_checkout(...) / stateProxy.checkoutSignal(...); none treat the returned value as a callable signal, so returning signals.computedSignal() (a snapshot) matches current consumers.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)

225-261: Operation de-duplication: good pattern

Pending-operation map prevents concurrent duplicate calls from racing. Nicely done.

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

101-101: Return type alignment looks good

__experimental_CheckoutFunction returning CheckoutSignalValue matches the new factory.

@panteliselef panteliselef changed the title Elef/bill 1098 update checkout public feat(clerk-js,shared): Align useCheckout with future hooks Sep 12, 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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)

214-220: Localize aria-label; avoid hard-coded text.

Per UI guidelines, localize all user-facing strings, including ARIA.

-        <SegmentedControl.Root
-          aria-label='Payment method source'
+        <SegmentedControl.Root
+          aria-label={`${localizationKeys('commerce.aria.paymentMethodSource')}`}

If localizationKeys isn’t supported for attributes, use useLocalizations().t('commerce.aria.paymentMethodSource').

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

353-358: Derive selected payment source from known list by id.

Removes potential stale reference and aligns with past guidance.

-    >(paymentSource || paymentSources.find(p => p.isDefault));
+    >(() => {
+      const byId = paymentSource ? paymentSources.find(p => p.id === paymentSource.id) : undefined;
+      return byId || paymentSources.find(p => p.isDefault);
+    });

34-38: Guard totals before use to prevent null deref (repeat).

totals is used unconditionally below (e.g., Lines 78-80, 115-118). Add the guard here.

-  if (!plan) {
+  if (!plan || !totals) {
     return null;
   }

195-201: Default paymentSources to an empty array.

Prevents length access on undefined and stabilizes initial state.

-  const { data: paymentSources } = usePaymentMethods();
+  const { data: paymentSources = [] } = usePaymentMethods();
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

17-18: Module-level cache risks leaks across sessions; scope or clear it.

Attach cache to the Clerk instance or export a clear function and invoke on sign-out/org change.

-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
+const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>();
+
+export const __internal_clearCheckoutCache = () => {
+  cache.clear();
+};

Follow-up: call __internal_clearCheckoutCache() on sign out and when the active org changes.

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

73-89: Fix “SignIn” copy/paste in Checkout docs.

Update JSDoc to reference Checkout and its resource.

-/**
- * The value returned by the `useSignInSignal` hook.
- */
+/**
+ * The value returned by the Checkout signal.
+ */
 export interface CheckoutSignalValue {
   /**
-   * Represents the errors that occurred during the last fetch of the parent resource.
+   * Represents errors from the last fetch of the Checkout resource.
    */
   errors: GlobalErrors;
   /**
-   * The fetch status of the underlying `SignIn` resource.
+   * The fetch status of the underlying Checkout resource.
    */
   fetchStatus: 'idle' | 'fetching';
   /**
-   * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows.
+   * An instance representing the current Checkout, with APIs designed for custom flows.
    */
   checkout: CheckoutFutureResource;
 }
🧹 Nitpick comments (6)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (3)

78-80: Unconditional access to totals.* can crash.

This relies on the previous guard. If you prefer rendering a placeholder, gate each group instead.

Also applies to: 115-118


156-163: Remove @ts-expect-error; pass unknown cleanly to handler.

Keep type-safety without suppressions.

-    if (error) {
-      // @ts-expect-error - error is not an Error
-      handleError(error, [], card.setError);
+    if (error) {
+      handleError(error as unknown, [], card.setError);

167-176: Validate form data before confirm.

FormData.get may return null. Add a guard to avoid sending invalid params.

   const payWithExistingPaymentSource = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     const data = new FormData(e.currentTarget);
-    const paymentSourceId = data.get('payment_source_id') as string;
+    const paymentSourceId = data.get('payment_source_id');
+    if (!paymentSourceId || typeof paymentSourceId !== 'string') {
+      return;
+    }
     return confirmCheckout({
       paymentSourceId,
     });
   };
packages/shared/src/react/hooks/useCheckout.ts (2)

40-52: Stabilize subscribe deps; include clerk.

Ensure resubscription when the instance changes.

-  const subscribe = useCallback(
+  const subscribe = useCallback(
     (callback: () => void) => {
       if (!clerk.loaded) {
         return () => {};
       }
       return clerk.__internal_state.__internal_effect(() => {
         signal();
         callback();
       });
     },
-    [signal, clerk.loaded, clerk.__internal_state],
+    [signal, clerk, clerk.loaded, clerk.__internal_state],
   );

53-60: Remove cast by typing useSyncExternalStore.

Cleaner types; no as needed.

-  const getSnapshot = useCallback(() => {
-    return signal();
-  }, [signal]);
-
-  const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
-  return value as CheckoutSignalValue;
+  const getSnapshot = useCallback<() => CheckoutSignalValue>(() => signal(), [signal]);
+  const value = useSyncExternalStore<CheckoutSignalValue>(subscribe, getSnapshot, getSnapshot);
+  return value;
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

12-15: Tighten planPeriod type in cache key.

Use the declared union for safety.

-function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey {
+function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: import('@clerk/types').CommerceSubscriptionPlanPeriod }): CheckoutKey {
📜 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 6be9212 and ff4f413.

📒 Files selected for processing (20)
  • .changeset/angry-maps-mix.md (1 hunks)
  • packages/clerk-js/src/core/clerk.ts (2 hunks)
  • packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts (0 hunks)
  • packages/clerk-js/src/core/modules/checkout/instance.ts (3 hunks)
  • packages/clerk-js/src/core/modules/checkout/manager.ts (0 hunks)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (5 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/Checkout/index.tsx (1 hunks)
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx (3 hunks)
  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (1 hunks)
  • packages/react/src/isomorphicClerk.ts (1 hunks)
  • packages/react/src/stateProxy.ts (5 hunks)
  • packages/shared/src/react/commerce.tsx (2 hunks)
  • packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts (1 hunks)
  • packages/shared/src/react/hooks/useCheckout.ts (1 hunks)
  • packages/types/src/clerk.ts (2 hunks)
  • packages/types/src/commerce.ts (2 hunks)
  • packages/types/src/utils.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/clerk-js/src/core/modules/checkout/tests/manager.spec.ts
  • packages/clerk-js/src/core/modules/checkout/manager.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • .changeset/angry-maps-mix.md
  • packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
  • packages/shared/src/react/commerce.tsx
  • packages/shared/src/react/hooks/tests/useCheckout.type.spec.ts
  • packages/clerk-js/src/ui/components/Checkout/index.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
  • packages/types/src/commerce.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
  • packages/types/src/utils.ts
  • packages/clerk-js/src/core/clerk.ts
  • packages/react/src/stateProxy.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/parts.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{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/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.ts
packages/**/*.{ts,tsx,d.ts}

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

Packages should export TypeScript types alongside runtime code

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.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/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/core/modules/checkout/instance.ts
  • packages/types/src/clerk.ts
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/Checkout/CheckoutForm.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/Checkout/CheckoutForm.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/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (4)
packages/shared/src/react/hooks/useCheckout.ts (3)
packages/shared/src/react/contexts.tsx (3)
  • __experimental_CheckoutProvider (119-119)
  • useCheckoutContext (118-118)
  • useClerkInstanceContext (117-117)
packages/types/src/clerk.ts (1)
  • CheckoutSignalValue (76-89)
packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
  • useCheckoutContext (10-37)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (7)
packages/react/src/stateProxy.ts (1)
  • checkout (191-197)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
  • plan (170-172)
  • totals (176-178)
  • paymentSource (189-191)
packages/types/src/commerce.ts (2)
  • ConfirmCheckoutParams (1162-1188)
  • CommercePaymentSourceResource (487-534)
packages/clerk-js/src/ui/utils/errorHandler.ts (1)
  • handleError (69-91)
packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
  • usePaymentMethods (32-40)
packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
  • usePaymentMethods (9-21)
packages/types/src/utils.ts (1)
  • RemoveFunctions (127-129)
packages/clerk-js/src/core/modules/checkout/instance.ts (3)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
  • CheckoutFuture (146-223)
  • createSignals (128-144)
packages/clerk-js/src/core/clerk.ts (1)
  • Clerk (200-2948)
packages/types/src/clerk.ts (3)
  • Clerk (165-888)
  • __experimental_CheckoutOptions (95-99)
  • CheckoutSignalValue (76-89)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
  • FieldError (7-20)
packages/types/src/commerce.ts (3)
  • CheckoutFutureResource (1412-1427)
  • ForPayerType (122-122)
  • CommerceSubscriptionPlanPeriod (139-139)
⏰ 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). (26)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Static analysis
  • GitHub Check: Integration Tests (quickstart, chrome)
  • GitHub Check: Integration Tests (tanstack-react-router, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Unit Tests (18, --filter=@clerk/astro --filter=@clerk/backend --filter=@clerk/express --filter=@c...
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)

40-44: Return cached signal value is fine; ensure signals remain hot.

LGTM assuming callers subscribe via __internal_state. No change needed.

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

101-101: Return type alignment looks good.

__experimental_CheckoutFunction now returns CheckoutSignalValue; consistent with the new API.

Comment on lines +148 to 151
if (checkout.status !== 'needs_confirmation') {
throw new Error('Checkout not found');
}

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

Avoid throwing from hook logic; fail gracefully.

Throwing here will crash the tree when the status is not confirmable. Either gate rendering upstream or handle gracefully in the mutation.

-  if (checkout.status !== 'needs_confirmation') {
-    throw new Error('Checkout not found');
-  }
+  const ensureConfirmable = () => {
+    return checkout.status === 'needs_confirmation';
+  };

And inside confirmCheckout:

-  const confirmCheckout = async (params: ConfirmCheckoutParams) => {
+  const confirmCheckout = async (params: ConfirmCheckoutParams) => {
     card.setLoading();
     card.setError(undefined);
+    if (!ensureConfirmable()) {
+      card.setIdle();
+      return;
+    }
📝 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
if (checkout.status !== 'needs_confirmation') {
throw new Error('Checkout not found');
}
const ensureConfirmable = () => {
return checkout.status === 'needs_confirmation';
};
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines
148–151, do not throw an Error when checkout.status !== 'needs_confirmation';
instead fail gracefully by returning early or setting a local error/loading
state and render a fallback UI (or no-op) so the component tree doesn't crash;
likewise update confirmCheckout to avoid throwing synchronously—have it return a
rejected promise or set/return an error result that the caller/mutation can
handle, and ensure the upstream render is gated by a status check or consumes
the returned error state to show a proper message.

Comment on lines +31 to +38
const signal = useCallback(() => {
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
}, [
// user?.id, organization?.id,
planId,
planPeriod,
forOrganization,
]);
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

Include clerk in deps to avoid stale closure and pass ESLint.

clerk is captured; add it to the dependency array.

-  }, [
+  }, [
     // user?.id, organization?.id,
     planId,
     planPeriod,
     forOrganization,
+    clerk,
   ]);
📝 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 signal = useCallback(() => {
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
}, [
// user?.id, organization?.id,
planId,
planPeriod,
forOrganization,
]);
const signal = useCallback(() => {
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
}, [
// user?.id, organization?.id,
planId,
planPeriod,
forOrganization,
clerk,
]);
🤖 Prompt for AI Agents
In packages/shared/src/react/hooks/useCheckout.ts around lines 31 to 38, the
useCallback captures the `clerk` variable but it is not listed in the dependency
array; update the dependency array to include `clerk` (e.g. [clerk, planId,
planPeriod, forOrganization]) so the callback references the latest clerk and
satisfies ESLint.

Comment on lines +62 to +71
interface GlobalErrors {
/**
* The raw, unparsed errors from the Clerk API.
*/
raw: FieldError[] | null;
/**
* Parsed errors that are not related to any specific field.
*/
global: FieldError[] | null; // does not include any errors that could be parsed as a field error
}
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

Export GlobalErrors used in public API.

Referenced by exported CheckoutSignalValue; export it to avoid opaque types in d.ts.

-interface GlobalErrors {
+export interface GlobalErrors {
📝 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
interface GlobalErrors {
/**
* The raw, unparsed errors from the Clerk API.
*/
raw: FieldError[] | null;
/**
* Parsed errors that are not related to any specific field.
*/
global: FieldError[] | null; // does not include any errors that could be parsed as a field error
}
export interface GlobalErrors {
/**
* The raw, unparsed errors from the Clerk API.
*/
raw: FieldError[] | null;
/**
* Parsed errors that are not related to any specific field.
*/
global: FieldError[] | null; // does not include any errors that could be parsed as a field error
}
🤖 Prompt for AI Agents
In packages/types/src/clerk.ts around lines 62 to 71, the interface GlobalErrors
is currently unexported but is referenced by an exported type
(CheckoutSignalValue), causing opaque types in the generated d.ts; fix it by
adding an export modifier to the interface declaration (export interface
GlobalErrors { ... }) so it becomes part of the public API, and update any
imports/usages if necessary to use the exported name.

@panteliselef panteliselef requested a review from a team September 12, 2025 18:04
Comment on lines +95 to +126
function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
const parsedErrors: CheckoutSignalValue['errors'] = {
raw: null,
global: null,
};

if (!error) {
return parsedErrors;
}

if (!isClerkAPIResponseError(error)) {
parsedErrors.raw = [];
parsedErrors.global = [];
return parsedErrors;
}

error.errors.forEach(error => {
if (parsedErrors.raw) {
parsedErrors.raw.push(error);
} else {
parsedErrors.raw = [error];
}

if (parsedErrors.global) {
parsedErrors.global.push(error);
} else {
parsedErrors.global = [error];
}
});

return parsedErrors;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Error structure is WIP

__experimental_checkout(options: __experimental_CheckoutOptions): CheckoutSignalValue {
if (!this._checkout) {
this._checkout = params => createCheckoutInstance(this, params);
this._checkout = (params: any) => createCheckoutInstance(this, params);
Copy link
Member

Choose a reason for hiding this comment

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

Is this params possible to be typed?

subscribe,
getState: manager.getCacheState,
};
const checkout = new CheckoutFuture(signals, {
Copy link
Member

Choose a reason for hiding this comment

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

not saying you should do it this way, but in our SignInFuture class we don't directly access signals. The resources emit events and our state layer listens for those to propagate to the signals.

};
}

function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] {
Copy link
Member

Choose a reason for hiding this comment

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

just calling out we don't store this implementation alongside the resource, but that's because we have a dedicated signals file. not saying you need to move it, just wanted to point that out in case you can think of a better place that more closely aligns with signIn/signUp.

}

async start(): Promise<{ error: unknown }> {
return this.runAsyncResourceTask(
Copy link
Member

Choose a reason for hiding this comment

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

not loving that this is a slightly different implementation of runAsyncResourceTask. Can we come up with a single implantation that works for both use cases?

const { error } = await checkout.confirm(params);

if (error) {
// @ts-expect-error - error is not an Error
Copy link
Member

Choose a reason for hiding this comment

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

what TS rule was this breaking?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because Error is unknown

return gateProperty(target, 'externalClientSecret', null) as null;
},
get externalGatewayId() {
return gateProperty(target, 'externalGatewayId', null) as null;
Copy link
Member

Choose a reason for hiding this comment

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

are these casts necessary? the types should be pulling through gateProperty correctly.

import type { WaitlistResource } from './waitlist';

type __experimental_CheckoutStatus = 'needs_initialization' | 'needs_confirmation' | 'completed';
interface GlobalErrors {
Copy link
Member

Choose a reason for hiding this comment

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

what are your thoughts on putting this into the ./state file?

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