-
Notifications
You must be signed in to change notification settings - Fork 382
chore(clerk-js,types): Replace commerce with billing in resources #6757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
chore(clerk-js,types): Replace commerce with billing in resources #6757
Conversation
🦋 Changeset detectedLatest commit: a0a88ad The changes in this PR will be included in the next version bump. This PR includes changesets to release 22 packages
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 |
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRenames public APIs and types from "Commerce" to "Billing" across packages: backend, clerk-js, shared React hooks/components, UI, and @clerk/types. Money-amount, plan/subscription/payment/statement resources, warnings, utilities, and some resourceType/string keys are updated; a Billing alias hook is added. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App
participant Clerk as Clerk SDK
participant Billing as Billing Namespace
participant API as Backend API
App->>Clerk: clerk.billing.startCheckout(params)
Clerk->>Billing: Billing.startCheckout(params)
Billing->>API: POST /.../checkout (sends BillingCheckoutJSON)
API-->>Billing: BillingCheckoutJSON
Billing-->>Clerk: BillingCheckout (resource)
Clerk-->>App: BillingCheckoutResource
Note over App,Billing: Confirm flow
App->>Billing: checkout.confirm()
Billing->>API: POST /.../checkout/confirm
API-->>Billing: BillingCheckoutJSON (updated)
Billing-->>App: Updated BillingCheckoutResource
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
✨ Finishing touches
🧪 Generate unit tests
Comment |
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/elements
@clerk/clerk-expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/clerk-react
@clerk/react-router
@clerk/remix
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/themes
@clerk/types
@clerk/upgrade
@clerk/vue
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (1)
283-284
: Hard-coded English "upcoming" breaks localization.
Replace the inline string with a localized prefix.Use a localized label and a derived display name:
// inside SubscriptionDetailsFooter const { t } = useLocalizations(); const planDisplay = selectedSubscription.status === 'upcoming' ? `${t(localizationKeys('commerce.upcoming'))} ${selectedSubscription.plan.name}` : selectedSubscription.plan.name; // Then in the title's localization params: localizationKeys('commerce.cancelSubscriptionTitle', { plan: planDisplay })packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx (1)
115-118
: Avoid in-place sort; it mutates hook data.Sorting
paymentMethods
directly can mutate shared state and cause subtle bugs. Copy before sorting and return a stable comparator.- const sortedPaymentSources = useMemo( - () => paymentMethods.sort((a, b) => (a.isDefault && !b.isDefault ? -1 : 1)), - [paymentMethods], - ); + const sortedPaymentSources = useMemo( + () => + [...paymentMethods].sort((a, b) => { + if (a.isDefault === b.isDefault) return 0; + return a.isDefault ? -1 : 1; + }), + [paymentMethods], + );packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx (1)
53-68
: Fix comparator and avoid mutatingsubscriptionItems
.
- Current comparator returns
1
for most non-active pairs, which is unstable and incorrect.- Sorting in place mutates source data.
- const sortedSubscriptions = useMemo( - () => - subscriptionItems.sort((a, b) => { - // always put active subscriptions first - if (a.status === 'active' && b.status !== 'active') { - return -1; - } - - if (b.status === 'active' && a.status !== 'active') { - return 1; - } - - return 1; - }), - [subscriptionItems], - ); + const sortedSubscriptions = useMemo( + () => + [...subscriptionItems].sort((a, b) => { + const aActive = a.status === 'active'; + const bActive = b.status === 'active'; + if (aActive && !bActive) return -1; + if (bActive && !aActive) return 1; + return 0; + }), + [subscriptionItems], + );packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)
210-215
: Localize the aria-label.Hard-coded aria-label violates the localization guideline for user-facing strings. Use a localization key.
- <SegmentedControl.Root - aria-label='Payment method source' + <SegmentedControl.Root + aria-label={t => t.localize(localizationKeys('commerce.checkout.paymentMethodSourceAriaLabel'))}If
SegmentedControl.Root
supports anariaLabel
/ariaLabelKey
prop, prefer that variant.packages/clerk-js/src/core/resources/CommercePaymentSource.ts (2)
46-58
: Add explicit return type and JSDoc; keep API contracts clear.Return type is currently inferred; declare it explicitly and document params/return.
Apply:
- public async remove(params?: RemovePaymentSourceParams) { + public async remove(params?: RemovePaymentSourceParams): Promise<DeletedObject> {
60-71
: Avoidany
in request body and add explicit return type.Use a precise type for the payload and declare the return type.
Apply:
- public async makeDefault(params?: MakeDefaultPaymentSourceParams) { + public async makeDefault(params?: MakeDefaultPaymentSourceParams): Promise<null> { const { orgId } = params ?? {}; await BaseResource._fetch({ path: orgId ? `/organizations/${orgId}/commerce/payers/default_payment_source` : `/me/commerce/payers/default_payment_source`, method: 'PUT', - body: { payment_source_id: this.id } as any, + body: { payment_source_id: this.id } satisfies { payment_source_id: string }, });packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)
56-87
: Tighten types & correct backoff comment — packages/clerk-js/src/core/resources/CommerceCheckout.ts
- Change
shouldRetry(error: any, iterations: number)
toshouldRetry(error: unknown, iterations: number)
(matches retry.ts signature).- Remove
as any
onbody
; pass the typed param (body: params
) or call_basePatch
with an explicit generic (this._basePatch<ConfirmCheckoutParams>({ ..., body: params })
) if the compiler requires — avoidany
.- Fix the comment: with
initialDelay = 2000
,maxDelayBetweenRetries = 2000
,factor = 1.1
,jitter = false
delays are constant ≈2s for each retry (3 retries). Either update the comment to say "constant 2s delay between attempts" or changemaxDelayBetweenRetries
/factor
to implement increasing backoff.- Confirm the
/commerce/checkouts/.../confirm
path is the intended endpoint post-rename and plan a migration if backend paths move to/billing/...
.packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts (1)
1-18
: Add explicit return type for startCheckout and import the resource type.Public API should declare return types; align with
BillingNamespace#startCheckout
.@@ import type { BillingCheckoutJSON, + BillingCheckoutResource, BillingNamespace, @@ - startCheckout = async (params: CreateCheckoutParams) => { + startCheckout = async (params: CreateCheckoutParams): Promise<BillingCheckoutResource> => { const { orgId, ...rest } = params; const json = ( await BaseResource._fetch<BillingCheckoutJSON>({ path: orgId ? `/organizations/${orgId}/commerce/checkouts` : `/me/commerce/checkouts`, method: 'POST', body: rest as any, }) )?.response as unknown as BillingCheckoutJSON; return new BillingCheckout(json); };Also applies to: 123-134
🧹 Nitpick comments (41)
.changeset/yummy-snails-work.md (1)
8-8
: Add BREAKING/migration notes or explicit deprecation policy.Clarify impact and how to migrate. If aliases exist, note stability and removal timeline; if not, call out the breaking rename and add a link to docs/codemod.
Proposed replacement:
-[Billing Beta] Rename types, interfaces and classes that contain `commerce` to use `billing` instead. +[Billing Beta] Rename all public `Commerce*` types, interfaces, classes, and resource identifiers to `Billing*`. + +BREAKING: +- If no deprecation aliases are provided, this release removes `Commerce*` exports and requires migrating imports and type references to `Billing*`. +- If deprecation aliases are provided, `Commerce*` exports remain as deprecated and will be removed in the next major. Please migrate to `Billing*`. + +Migration: +- Replace imports/usages: `CommerceX` → `BillingX` (plans, subscriptions, payments, statements, checkout, money amounts, resourceType strings, hooks). +- See docs/changelog for a complete mapping and examples.packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx (2)
151-159
: Consider localizing payment status text
{paymentAttempt.status}
is rendered verbatim. If you expose statuses to end users, prefer localized labels or a status-to-key map.
178-181
: Migrate i18n keys from commerce. → billing. (or add alias mappings)**Multiple UI files still call localizationKeys('commerce.'); update to 'billing.' or add alias entries in your i18n bundle to avoid drift. Example locations: packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx (≈178, 239, 246), packages/clerk-js/src/ui/contexts/components/Plans.tsx, packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx. Locate all occurrences with:
rg -n --hidden --no-ignore-vcs -S "localizationKeys(['"]commerce." packages/clerk-js/src/uipackages/backend/src/api/resources/CommerceSubscription.ts (1)
6-10
: Docstring still references CommerceSubscriptionResourceUpdate the reference to the Billing equivalent to avoid confusion.
- * The `CommerceSubscription` object is similar to the [`CommerceSubscriptionResource`](/docs/references/javascript/types/commerce-subscription-resource) object + * The `CommerceSubscription` object is similar to the [`BillingSubscriptionResource`](/docs/references/javascript/types/billing-subscription-resource) objectpackages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptsList.tsx (1)
15-40
: Guard rows during loading and add explicit return typesPrevent potential runtime errors when
paymentAttempts
is temporarily undefined and prefer explicit return types for exported/components.-export const PaymentAttemptsList = () => { +export const PaymentAttemptsList = (): JSX.Element => { const { data: paymentAttempts, isLoading, count } = usePaymentAttempts(); ... - rows={paymentAttempts.map(i => ( + rows={(paymentAttempts ?? []).map(i => ( <PaymentAttemptsListRow key={i.id} paymentAttempt={i} /> ))}Also:
-const PaymentAttemptsListRow = ({ paymentAttempt }: { paymentAttempt: BillingPaymentResource }) => { +const PaymentAttemptsListRow = ({ paymentAttempt }: { paymentAttempt: BillingPaymentResource }): JSX.Element => {Optionally localize status text instead of rendering
{status}
directly.Also applies to: 43-86
packages/backend/src/api/resources/CommerceSubscriptionItem.ts (2)
93-106
: Deduplicate money JSON mapping via shared helperYou’re re-implementing the JSON→money mapping. Prefer using/centralizing a single helper (e.g.,
billingMoneyAmountFromJSON
) to avoid drift.I can extract a shared helper or wire this to the existing one if present.
6-10
: Docstring mentions Commerce endpointsIf this resource is now part of the Billing rename, refresh the links/descriptions to reference Billing docs to prevent confusion.
- ... in the [Backend API](https://clerk.com/docs/reference/backend-api/tag/commerce/get/commerce/subscription_items) + ... in the [Backend API](https://clerk.com/docs/reference/backend-api/tag/billing/get/billing/subscription_items)packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx (1)
80-91
: Updated generics to Billing types — OK; consider removingas any
.
If you want to avoidas any
, we can tighten the return type with a discriminated union and avoid the cast.packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx (1)
52-70
: selectPlan typing verified — handler & checkout props use Billing types
- Confirmed: handleSelectPlan expects plan: BillingPlanResource and planPeriod: BillingSubscriptionPlanPeriod (packages/clerk-js/src/ui/contexts/components/Plans.tsx).
- Confirmed: __internal_CheckoutProps defines planPeriod: BillingSubscriptionPlanPeriod (packages/types/src/clerk.ts).
- Optional nitpick: add explicit return type
: void
to selectPlan (packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx).packages/shared/src/react/hooks/createCommerceHook.tsx (1)
114-116
: Public alias added; add JSDoc and consider deprecating the Commerce-named factoryAdd a short JSDoc so the alias shows up correctly in API docs and guides users toward the Billing naming.
-// Billing alias factory for paginated hooks -export const createBillingPaginatedHook = createCommercePaginatedHook; +/** + * Public alias for billing resources. Mirrors `createCommercePaginatedHook`. + * @public + */ +export const createBillingPaginatedHook = createCommercePaginatedHook;Optionally, mark
createCommercePaginatedHook
as deprecated in its JSDoc to steer new usage to the Billing alias.packages/shared/src/react/hooks/useCheckout.ts (1)
43-46
: Update comment to reference Billing typesDoc still mentions “CommerceCheckoutResource”; update to “BillingCheckoutResource”.
- * On status === 'needs_confirmation' or 'completed', all properties are defined the same as the CommerceCheckoutResource. + * On status === 'needs_confirmation' or 'completed', all properties are defined the same as the BillingCheckoutResource.packages/clerk-js/src/ui/components/Statements/StatementsList.tsx (1)
1-1
: Type rename to Billing is consistentProp and import updates are correct. Optional: the
onPageChange={_ => {}}
no-op suggests pagination isn’t wired—ensure this is intentional for the current scope.Also applies to: 42-42
packages/clerk-js/src/ui/components/Subscriptions/__tests__/SubscriptionsList.test.tsx (1)
318-318
: Fix typo in test title ("upcomming" → "upcoming").Keeps test descriptions clean.
- it('renders upcomming badge when current subscription is canceled but active', async () => { + it('renders upcoming badge when current subscription is canceled but active', async () => {packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx (1)
65-71
: Prefer useMemo over useRef for derived identifier string.
ref.current
is always truthy with the current template; the null-guard never triggers. UseuseMemo
for clarity and drop the dead check.- const ref = useRef( - `${paymentSource.paymentMethod === 'card' ? paymentSource.cardType : paymentSource.paymentMethod} ${paymentSource.paymentMethod === 'card' ? `⋯ ${paymentSource.last4}` : '-'}`, - ); - - if (!ref.current) { - return null; - } + const identifier = useMemo( + () => + `${paymentSource.paymentMethod === 'card' ? paymentSource.cardType : paymentSource.paymentMethod} ${ + paymentSource.paymentMethod === 'card' ? `⋯ ${paymentSource.last4}` : '-' + }`, + [paymentSource.cardType, paymentSource.last4, paymentSource.paymentMethod], + );And update usages:
- identifier: ref.current, + identifier,- paymentSource: ref.current, + paymentSource: identifier,packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx (1)
163-163
: Redundant key on .Parent already assigns
key
to . The innerkey
is unnecessary.- <Tr key={subscription.id}> + <Tr>packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)
356-360
: Improve label formatting for non-card methods.
capitalize
will renderapple_pay
asApple_pay
. Consider humanizing wallet names.- const label = - source.paymentMethod !== 'card' - ? `${capitalize(source.paymentMethod)}` - : `${capitalize(source.cardType)} ⋯ ${source.last4}`; + const pretty = (s: string) => s.replace(/[_-]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + const label = + source.paymentMethod !== 'card' + ? pretty(source.paymentMethod) + : `${pretty(source.cardType)} ⋯ ${source.last4}`;packages/shared/src/react/hooks/usePaymentAttempts.tsx (1)
4-4
: Rename the alias to avoid confusion.Use the actual name to reduce cognitive overhead.
-import { createBillingPaginatedHook as createCommercePaginatedHook } from './createCommerceHook'; +import { createBillingPaginatedHook } from './createCommerceHook'; @@ -export const usePaymentAttempts = createCommercePaginatedHook<BillingPaymentResource, GetPaymentAttemptsParams>({ +export const usePaymentAttempts = createBillingPaginatedHook<BillingPaymentResource, GetPaymentAttemptsParams>({Also applies to: 9-9
packages/backend/src/api/resources/CommercePlan.ts (1)
71-79
: Annotate helper’s return type for clarity.Make the return type explicit to match project TS guidelines.
- const formatAmountJSON = (fee: CommercePlanJSON['fee']) => { + const formatAmountJSON = (fee: CommercePlanJSON['fee']): BillingMoneyAmount => { return { amount: fee.amount, amountFormatted: fee.amount_formatted, currency: fee.currency, currencySymbol: fee.currency_symbol, }; };packages/clerk-js/src/utils/commerce.ts (1)
10-17
: Renamed money conversion looks good; add temporary aliases to ease migration.Provide deprecated aliases so downstreams don’t break immediately.
// existing function unchanged export const billingMoneyAmountFromJSON = (data: BillingMoneyAmountJSON): BillingMoneyAmount => { return { amount: data.amount, amountFormatted: data.amount_formatted, currency: data.currency, currencySymbol: data.currency_symbol, }; }; + +// TODO: Remove in next major +/** @deprecated Use billingMoneyAmountFromJSON */ +export const commerceMoneyAmountFromJSON = billingMoneyAmountFromJSON;Additionally (outside the shown range), consider adding:
// TODO: Remove in next major /** @deprecated Use billingTotalsFromJSON */ export const commerceTotalsFromJSON = billingTotalsFromJSON;packages/shared/src/react/hooks/usePlans.tsx (1)
4-4
: Rename alias to reduce cognitive overhead (use “Billing” consistently).Import
createBillingPaginatedHook
without aliasing it to Commerce, and use it directly when creating the hook.-import { createBillingPaginatedHook as createCommercePaginatedHook } from './createCommerceHook'; +import { createBillingPaginatedHook } from './createCommerceHook'; @@ -export const usePlans = createCommercePaginatedHook<BillingPlanResource, GetPlansParams>({ +export const usePlans = createBillingPaginatedHook<BillingPlanResource, GetPlansParams>({Also applies to: 9-9
packages/clerk-js/src/ui/components/PricingTable/PricingTableMatrix.tsx (3)
132-140
: Remove hard-coded fallback text when using localization keys.Guideline: don’t render hard-coded strings. Drop the literal “Billing cycle” and rely solely on
localizationKey
.- <Text + <Text id={segmentedControlId} colorScheme='secondary' variant='caption' localizationKey={localizationKeys('commerce.pricingTable.billingCycle')} - > - Billing cycle - </Text> + />
141-144
: TightenSegmentedControl
typing to avoid casting.If
SegmentedControl.Root
supports generics, parameterize it withBillingSubscriptionPlanPeriod
to drop theas
cast.- <SegmentedControl.Root + <SegmentedControl.Root<BillingSubscriptionPlanPeriod> aria-labelledby={segmentedControlId} value={planPeriod} - onChange={value => setPlanPeriod(value as BillingSubscriptionPlanPeriod)} + onChange={value => setPlanPeriod(value)} >
200-208
: Guard against empty plan names when deriving initials.
plan.name[0]
can throw ifname
is an empty string. Use optional chaining with a fallback.- initials={plan.name[0]} + initials={plan.name?.[0] ?? ''}packages/clerk-js/src/core/resources/CommercePayment.ts (1)
15-47
: Rename and JSON mapping look correct; consider adding JSDoc for the public resource.The class aligns with
BillingPaymentResource
and uses the Billing JSON helpers. Add a short JSDoc to meet the “public APIs must be documented” guideline.+/** + * Billing payment resource. + * @public + */ export class BillingPayment extends BaseResource implements BillingPaymentResource {packages/clerk-js/src/core/resources/CommercePaymentSource.ts (1)
74-94
: Document experimental surface on the initializer resource.Add a brief class-level JSDoc (experimental notice) and property docs (externalClientSecret, externalGatewayId, paymentMethodOrder).
packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)
13-16
: Align filenames with renamed types.Imports refer to
./CommercePayer
while class isBillingPayer
. Consider renaming the file toBillingPayer.ts
(and this file toBillingCheckout.ts
) for consistency in a follow-up.packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts (2)
188-191
: DRY the repeated mocked function type.Introduce a small alias to reduce noise and improve readability.
Apply near the top:
+type AsyncCheckoutOp = MockedFunction<() => Promise<BillingCheckoutResource>>;
Then replace occurrences like:
-const mockOperation: MockedFunction<() => Promise<BillingCheckoutResource>> = vi.fn().mockResolvedValue(mockCheckout); +const mockOperation: AsyncCheckoutOp = vi.fn().mockResolvedValue(mockCheckout);Also applies to: 214-221, 234-237, 259-262, 274-277, 293-296, 318-324, 337-340, 362-364, 409-415, 435-438, 461-463, 642-644, 670-673, 687-693
362-364
: Use fake timers to avoid real timeouts in concurrency tests.Real
setTimeout
can slow tests and be flaky under load.Example:
beforeEach(() => { vi.useFakeTimers(); }); it('deduplicates concurrent start ops', async () => { const op: AsyncCheckoutOp = vi.fn().mockImplementation( () => new Promise(resolve => setTimeout(() => resolve(createMockCheckoutResource()), 50)), ); const p = Promise.all([manager.executeOperation('start', op), manager.executeOperation('start', op)]); await vi.advanceTimersByTimeAsync(50); await p; });Want me to push a test refactor PR with fake timers?
Also applies to: 409-415, 687-693
packages/types/src/clerk.ts (2)
1062-1076
: Experimental flag still namedcommerce
. Confirm intent or add abilling
alias.If the product surface moved to “Billing”, consider introducing
experimental.billing?: boolean
(keepingcommerce
for back-compat, deprecating it in docs).
587-591
: Wording nit: “commerce functionality” → “billing functionality”.Update the docstring for clarity with the new naming.
Apply:
- * Loads Stripe libraries for commerce functionality + * Loads Stripe libraries for billing functionalitypackages/clerk-js/src/core/modules/commerce/CommerceBilling.ts (2)
31-46
: Preferawait
over.then
for consistency and readability.Minor cleanup; apply a consistent async style across methods.
Also applies to: 64-81, 94-112
30-30
: Add JSDoc to the public Billing namespace.Document that this is experimental and surfaces Billing endpoints under
/commerce/*
paths to avoid confusion.@@ +/** + * Billing namespace client. + * @experimental Subject to change while in public beta. + * Note: Underlying API paths still use `/commerce/*`. + */ export class Billing implements BillingNamespace {packages/types/src/json.ts (2)
892-894
: Remove stale TODO forfree_trial_ends_at
.The field is now required (
number | null
), so the TODO is obsolete.@@ - // TODO(@COMMERCE): Remove optional after GA. free_trial_ends_at: number | null;
653-680
: Clarify discriminants still readcommerce_*
while types are Billing.*To reduce developer confusion during the rename, add a short JSDoc note on these DTOs that
object
remainscommerce_*
for wire compatibility.Also applies to: 724-731, 755-767, 808-828, 880-896
packages/clerk-js/src/core/resources/CommerceSubscription.ts (1)
71-73
: Refresh TODO wording.Align the tag with Billing and clarify the open question.
- //TODO(@COMMERCE): Why can this be undefined ? + // TODO(@BILLING): Confirm when `amount` is expected to be undefined (e.g., free plan/legacy items).packages/types/src/commerce.ts (5)
511-514
: Prefer optional property over union with undefined.- walletType: string | undefined; + walletType?: string;
1225-1231
: JSDoc/type mismatch for freeTrialEndsAt.Doc says “Unix timestamp (milliseconds)” but the type is Date | null.
- /** - * Unix timestamp (milliseconds) of when the free trial ends. - */ + /** + * Date when the free trial ends. + */ freeTrialEndsAt: Date | null;
239-246
: Clarify annualMonthlyFee semantics.Name is ambiguous. At minimum, document it as “monthly equivalent when billed annually.”
/** * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. * It is advised to pin the SDK version and the clerk-js version to a specific version to avoid breaking changes. * @example * ```tsx * <ClerkProvider clerkJsVersion="x.x.x" /> * ``` */ - annualMonthlyFee: BillingMoneyAmount; + /** + * Monthly equivalent when billed annually (e.g. $20/mo if annualFee is $240). + */ + annualMonthlyFee: BillingMoneyAmount;
807-809
: Update leftover TODO namespace; decide nullability.- //TODO(@COMMERCE): should this be nullable ? + // TODO(@BILLING): should this be nullable?If nullable, change to
paymentSourceId: string | null;
.
1-4
: File name still commerce.ts.Consider renaming to billing.ts and re-export from commerce.ts to avoid breaking imports.
Example new file (outside this diff):
// packages/types/src/billing.ts export * from './commerce';And keep commerce.ts with a deprecation JSDoc on the module.
packages/clerk-js/src/core/resources/CommerceFeature.ts (1)
17-29
: JSON mapping is correct; tiny readability nit (optional).A small destructuring makes field assignments slightly clearer.
- protected fromJSON(data: FeatureJSON | null): this { + protected fromJSON(data: FeatureJSON | null): this { if (!data) { return this; } - - this.id = data.id; - this.name = data.name; - this.description = data.description; - this.slug = data.slug; - this.avatarUrl = data.avatar_url; + const { id, name, description, slug, avatar_url } = data; + this.id = id; + this.name = name; + this.description = description; + this.slug = slug; + this.avatarUrl = avatar_url;
'@clerk/clerk-js': minor | ||
'@clerk/types': minor | ||
'@clerk/backend': patch | ||
'@clerk/shared': patch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Semver levels likely incorrect for public API/type renames — should be major.
Renaming exported types/classes from Commerce* → Billing* is a breaking change for consumers. Unless you ship full backwards-compatible aliases for all renamed public exports (and keep them in the public surface), bump these packages to major to avoid releasing a breaking change under minor/patch.
Apply this diff if there are breaking changes:
-'@clerk/clerk-js': minor
-'@clerk/types': minor
-'@clerk/backend': patch
-'@clerk/shared': patch
+'@clerk/clerk-js': major
+'@clerk/types': major
+'@clerk/backend': major
+'@clerk/shared': major
If you do have deprecation aliases covering all renamed exports, keep minor but explicitly state that in the description (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.
'@clerk/clerk-js': minor | |
'@clerk/types': minor | |
'@clerk/backend': patch | |
'@clerk/shared': patch | |
'@clerk/clerk-js': major | |
'@clerk/types': major | |
'@clerk/backend': major | |
'@clerk/shared': major |
🤖 Prompt for AI Agents
.changeset/yummy-snails-work.md around lines 2 to 5: The changeset lists
minor/patch bumps for packages where exported types/classes were renamed from
Commerce* to Billing*, which is a breaking API change for consumers; either
change the version bumps to major for any package that removed/renamed public
exports, or add and keep backwards-compatible aliases (exporting the old
Commerce* names re-exporting the new Billing* symbols) in the public surface so
the change truly remains non-breaking; update the changeset to use major for the
affected packages unless you add comprehensive deprecation aliases and then
document that in the changeset description.
const { data: plans, total_count } = res as unknown as ClerkPaginatedResponse<BillingPlanJSON>; | ||
|
||
return { | ||
total_count, | ||
data: plans.map(plan => new CommercePlan(plan)), | ||
data: plans.map(plan => new BillingPlan(plan)), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix inconsistent _fetch response handling (.response vs raw).
Some methods read from res?.response
while others treat res
as the payload. This will break at runtime if _fetch
consistently wraps responses. Normalize to res?.response
in all methods below.
@@
- }).then(res => {
- const { data: plans, total_count } = res as unknown as ClerkPaginatedResponse<BillingPlanJSON>;
+ }).then(res => {
+ const { data: plans, total_count } =
+ (res as any)?.response as ClerkPaginatedResponse<BillingPlanJSON>;
return {
total_count,
data: plans.map(plan => new BillingPlan(plan)),
};
});
@@
- })) as unknown as BillingPlanJSON;
+ ))?.response as unknown as BillingPlanJSON;
return new BillingPlan(plan);
@@
- }).then(res => {
- const { data: payments, total_count } = res as unknown as ClerkPaginatedResponse<BillingPaymentJSON>;
+ }).then(res => {
+ const { data: payments, total_count } =
+ (res as any)?.response as ClerkPaginatedResponse<BillingPaymentJSON>;
return {
total_count,
data: payments.map(payment => new BillingPayment(payment)),
};
});
@@
- })) as unknown as BillingPaymentJSON;
+ ))?.response as unknown as BillingPaymentJSON;
return new BillingPayment(paymentAttempt);
Also applies to: 53-55, 104-109, 119-121
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts around lines
39-44 (and similarly update lines 53-55, 104-109, 119-121), the code treats the
fetch result inconsistently by reading payload directly from res instead of from
res?.response; update each affected method to read the payload from
res?.response (e.g., const { data: plans, total_count } = res?.response as
ClerkPaginatedResponse<BillingPlanJSON>) and adjust subsequent
mappings/constructors accordingly so all methods consistently use res?.response
as the actual payload.
import type { | ||
AddPaymentSourceParams, | ||
BillingInitializedPaymentSourceJSON, | ||
BillingPaymentSourceJSON, | ||
ClerkPaginatedResponse, | ||
CommerceInitializedPaymentSourceJSON, | ||
CommercePaymentSourceJSON, | ||
GetPaymentSourcesParams, | ||
InitializePaymentSourceParams, | ||
} from '@clerk/types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add explicit return types and remove any
casts in request bodies.
Public APIs should have explicit return types; avoid any
in bodies by typing the destructured rest.
import type {
AddPaymentSourceParams,
BillingInitializedPaymentSourceJSON,
BillingPaymentSourceJSON,
ClerkPaginatedResponse,
GetPaymentSourcesParams,
InitializePaymentSourceParams,
+ BillingPaymentSourceResource,
} from '@clerk/types';
@@
-export const initializePaymentSource = async (params: InitializePaymentSourceParams) => {
- const { orgId, ...rest } = params;
+export const initializePaymentSource = async (
+ params: InitializePaymentSourceParams,
+): Promise<BillingInitializedPaymentSource> => {
+ const { orgId, ...body }: InitializePaymentSourceParams & { orgId?: string } = params;
const json = (
await BaseResource._fetch({
path: orgId
? `/organizations/${orgId}/commerce/payment_sources/initialize`
: `/me/commerce/payment_sources/initialize`,
method: 'POST',
- body: rest as any,
+ body,
})
)?.response as unknown as BillingInitializedPaymentSourceJSON;
return new BillingInitializedPaymentSource(json);
};
@@
-export const addPaymentSource = async (params: AddPaymentSourceParams) => {
- const { orgId, ...rest } = params;
+export const addPaymentSource = async (params: AddPaymentSourceParams): Promise<BillingPaymentSource> => {
+ const { orgId, ...body }: AddPaymentSourceParams & { orgId?: string } = params;
@@
const json = (
await BaseResource._fetch({
path: orgId ? `/organizations/${orgId}/commerce/payment_sources` : `/me/commerce/payment_sources`,
method: 'POST',
- body: rest as any,
+ body,
})
)?.response as unknown as BillingPaymentSourceJSON;
return new BillingPaymentSource(json);
};
@@
-export const getPaymentSources = async (params: GetPaymentSourcesParams) => {
- const { orgId, ...rest } = params;
+export const getPaymentSources = async (
+ params: GetPaymentSourcesParams,
+): Promise<ClerkPaginatedResponse<BillingPaymentSourceResource>> => {
+ const { orgId, ...rest } = params;
@@
- }).then(res => {
+ }).then(res => {
const { data: paymentSources, total_count } =
res?.response as unknown as ClerkPaginatedResponse<BillingPaymentSourceJSON>;
return {
total_count,
- data: paymentSources.map(paymentSource => new BillingPaymentSource(paymentSource)),
+ data: paymentSources.map(paymentSource => new BillingPaymentSource(paymentSource)),
};
});
};
Also applies to: 13-25, 27-38, 40-55
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/modules/commerce/payment-source-methods.ts around
lines 1-8, 13-25, 27-38 and 40-55, the exported functions are missing explicit
return types and construct request bodies using `any` casts; update each
function signature to include the correct explicit return type (e.g.,
ClerkPaginatedResponse<BillingPaymentSourceJSON>, BillingPaymentSourceJSON,
BillingInitializedPaymentSourceJSON, etc. based on the operation) and eliminate
`any` by properly typing the destructured rest/params used to build the request
body (declare a typed object or interface for the body or cast the destructured
variable to the specific param type like
AddPaymentSourceParams/InitializePaymentSourceParams before using it), then
ensure the request call uses the strongly typed body and the function returns
the typed response.
export class BillingCheckout extends BaseResource implements BillingCheckoutResource { | ||
id!: string; | ||
externalClientSecret!: string; | ||
externalGatewayId!: string; | ||
paymentSource?: CommercePaymentSource; | ||
plan!: CommercePlan; | ||
planPeriod!: CommerceSubscriptionPlanPeriod; | ||
paymentSource?: BillingPaymentSource; | ||
plan!: BillingPlan; | ||
planPeriod!: BillingSubscriptionPlanPeriod; | ||
planPeriodStart!: number | undefined; | ||
status!: 'needs_confirmation' | 'completed'; | ||
totals!: CommerceCheckoutTotals; | ||
totals!: BillingCheckoutTotals; | ||
isImmediatePlanChange!: boolean; | ||
freeTrialEndsAt!: Date | null; | ||
payer!: CommercePayerResource; | ||
payer!: BillingPayerResource; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSDoc for public resource and its fields.
Class BillingCheckout and its fields (paymentSource, plan, planPeriod, totals, payer, etc.) are public surface—add JSDoc.
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/CommerceCheckout.ts around lines 17 to
30, the public class BillingCheckout and its public fields lack JSDoc comments;
add concise JSDoc blocks above the class and each exported/public field
(paymentSource, plan, planPeriod, planPeriodStart, status, totals,
isImmediatePlanChange, freeTrialEndsAt, payer, etc.) describing their purpose,
types/allowed values (e.g., status union), nullable/optional nature, and any
units or semantics (e.g., timestamps are epoch seconds), keeping descriptions
short and consistent with project style.
export class BillingPaymentSource extends BaseResource implements BillingPaymentSourceResource { | ||
id!: string; | ||
last4!: string; | ||
paymentMethod!: string; | ||
cardType!: string; | ||
isDefault!: boolean; | ||
isRemovable!: boolean; | ||
status!: CommercePaymentSourceStatus; | ||
status!: BillingPaymentSourceStatus; | ||
walletType: string | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSDoc for this public resource.
Per guidelines, public APIs need JSDoc (class summary and field docs: id, last4, paymentMethod, etc.).
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/CommercePaymentSource.ts around lines 14
to 22, the public BillingPaymentSource class and its fields lack JSDoc; add a
class-level JSDoc summary describing this resource (what it represents and when
it's used) and add JSDoc comments for each public field (id, last4,
paymentMethod, cardType, isDefault, isRemovable, status, walletType) describing
the value, type, and whether the field is optional or possible enum values
(e.g., status enum), keeping comments brief and following project JSDoc style.
planPeriod: BillingSubscriptionPlanPeriod; | ||
planId: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Public context prop now uses Billing plan period — OK; check downstream uses.
Run a sweep to catch lingering CommerceSubscriptionPlanPeriod
or mismatched types.
🏁 Script executed:
#!/bin/bash
# Find old plan-period type and usages of UseCheckoutOptions.planPeriod
rg -nP "CommerceSubscriptionPlanPeriod|UseCheckoutOptions\s*[\}\w\W]*planPeriod" -C3
Length of output: 2860
Replace remaining CommerceSubscriptionPlanPeriod usages with BillingSubscriptionPlanPeriod
Found 4 matches in tests — update the import/usages so types align with the public context change.
- packages/shared/src/react/hooks/tests/useCheckout.type.spec.ts — lines 6, 27, 36, 54
🤖 Prompt for AI Agents
In packages/shared/src/react/contexts.tsx around lines 30 to 31, the context
uses BillingSubscriptionPlanPeriod for planPeriod but tests still reference
CommerceSubscriptionPlanPeriod; update the four test occurrences in
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts (lines 6, 27,
36, 54) to import and use BillingSubscriptionPlanPeriod instead of
CommerceSubscriptionPlanPeriod, and search/replace any remaining
CommerceSubscriptionPlanPeriod type imports/usages in the test file to ensure
types align with the public context change.
type CheckoutProperties = Omit<RemoveFunctions<BillingCheckoutResource>, 'pathRoot' | 'status'>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix status
duplication/type mismatch in CheckoutProperties
CheckoutProperties
omits status
, but the null-state object includes status: null
and the destructured rest
still carries status
. This can cause excess-property issues and duplicate keys when composing checkout
. Remove status
from the null object and exclude it during destructuring.
type CheckoutProperties = Omit<RemoveFunctions<BillingCheckoutResource>, 'pathRoot' | 'status'>;
@@
const properties = useMemo<CheckoutProperties | ForceNull<CheckoutProperties>>(() => {
if (!managerProperties.checkout) {
return {
id: null,
externalClientSecret: null,
externalGatewayId: null,
- status: null,
totals: null,
isImmediatePlanChange: null,
planPeriod: null,
plan: null,
paymentSource: null,
freeTrialEndsAt: null,
payer: null,
};
}
const {
// eslint-disable-next-line @typescript-eslint/unbound-method
reload,
confirm,
pathRoot,
+ status: _status, // exclude to avoid duplication; status is provided from managerProperties below
// All the above need to be removed from the properties
...rest
} = managerProperties.checkout;
return rest;
}, [managerProperties.checkout]);
Also applies to: 103-128
🤖 Prompt for AI Agents
In packages/shared/src/react/hooks/useCheckout.ts around lines 30-31 and again
where the null-state object is created/used around lines 103-128, the
CheckoutProperties type omits 'status' but the null-state object still sets
status: null and destructuring leaves status in rest, causing duplicate
keys/extra-property type errors; remove the status property from the null-state
object and also exclude status during destructuring (e.g., omit it from rest) so
the composed checkout object does not include a duplicated or mismatched status
field.
packages/types/src/commerce.ts
Outdated
initializePaymentSource: ( | ||
params: Exclude<InitializePaymentSourceParams, 'orgId'>, | ||
) => Promise<CommerceInitializedPaymentSourceResource>; | ||
) => Promise<BillingInitializedPaymentSourceResource>; | ||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Omit instead of Exclude to remove orgId from param types.
Exclude does not remove object keys; it filters union members. This keeps orgId instead of removing it.
Apply:
- initializePaymentSource: (
- params: Exclude<InitializePaymentSourceParams, 'orgId'>,
- ) => Promise<BillingInitializedPaymentSourceResource>;
+ initializePaymentSource: (
+ params: Omit<InitializePaymentSourceParams, 'orgId'>,
+ ) => Promise<BillingInitializedPaymentSourceResource>;
...
- addPaymentSource: (params: Exclude<AddPaymentSourceParams, 'orgId'>) => Promise<BillingPaymentSourceResource>;
+ addPaymentSource: (params: Omit<AddPaymentSourceParams, 'orgId'>) => Promise<BillingPaymentSourceResource>;
...
- getPaymentSources: (
- params: Exclude<GetPaymentSourcesParams, 'orgId'>,
- ) => Promise<ClerkPaginatedResponse<BillingPaymentSourceResource>>;
+ getPaymentSources: (
+ params: Omit<GetPaymentSourcesParams, 'orgId'>,
+ ) => Promise<ClerkPaginatedResponse<BillingPaymentSourceResource>>;
Also applies to: 167-167, 176-179
🤖 Prompt for AI Agents
In packages/types/src/commerce.ts around lines 156-159 (and also at 167 and
176-179), the type manipulation uses Exclude to remove orgId from parameter
types but Exclude filters union members rather than omitting object keys;
replace Exclude<..., 'orgId'> with Omit<..., 'orgId'> for these function
parameter types so the orgId property is actually removed from the resulting
param type, updating all occurrences at the noted lines.
💡 Verification agent
🧩 Analysis chain
Repo-wide verification helper.
Check for other Exclude<..., 'orgId'>, missing BillingStatementGroup.id, and leftover @Commerce.
Also applies to: 167-167, 176-179, 752-765, 807-809, 1225-1231
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find Exclude<..., 'orgId'> misuse:"
rg -nP "Exclude<[^>]+,\s*['\"]orgId['\"]" -g '!**/dist/**' -g '!**/build/**'
echo -e "\nFind BillingStatementGroup definitions/usages:"
rg -nP -C3 '(interface|class)\s+BillingStatementGroup\b' packages
echo -e "\nLeftover @COMMERCE tags:"
rg -n "@COMMERCE" -n
echo -e "\nCheck freeTrialEndsAt doc mentions of 'Unix timestamp':"
rg -nP -C2 'freeTrialEndsAt.*Unix timestamp' packages/types/src
Length of output: 183
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "Git branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
echo "Top-level entries:"
ls -la | sed -n '1,200p' || true
echo "Tracked file count: $(git ls-files 2>/dev/null | wc -l || true)"
if command -v rg >/dev/null 2>&1; then
echo "Using rg (ripgrep)"
echo -e "\nFind Exclude<..., 'orgId'> misuse:"
rg -nP --hidden --no-ignore-vcs "Exclude<[^>]+,\s*['\"]orgId['\"]" || echo "no matches"
echo -e "\nFind BillingStatementGroup definitions/usages (interface|class|type) with context:"
rg -nP -C3 --hidden --no-ignore-vcs "(interface|class|type)\s+BillingStatementGroup\b" || echo "no matches"
echo -e "\nAll occurrences of BillingStatementGroup:"
rg -n --hidden --no-ignore-vcs "BillingStatementGroup" || echo "no matches"
echo -e "\nLeftover @COMMERCE tags:"
rg -n --hidden --no-ignore-vcs "@COMMERCE" || echo "no matches"
echo -e "\nCheck freeTrialEndsAt doc mentions of 'Unix timestamp':"
rg -nP -C2 --hidden --no-ignore-vcs "freeTrialEndsAt.*Unix timestamp" || echo "no matches"
else
echo "rg not found; falling back to git grep"
echo -e "\nFind Exclude<..., 'orgId'> misuse:"
git grep -n -E "Exclude<[^>]+,\s*['\"]orgId['\"]" || echo "no matches"
echo -e "\nFind BillingStatementGroup definitions/usages (interface|class|type) with context:"
git grep -n -E "(interface|class|type)\s+BillingStatementGroup\b" || echo "no matches"
echo -e "\nAll occurrences of BillingStatementGroup:"
git grep -n "BillingStatementGroup" || echo "no matches"
echo -e "\nLeftover @COMMERCE tags:"
git grep -n "@COMMERCE" || echo "no matches"
echo -e "\nCheck freeTrialEndsAt doc mentions of 'Unix timestamp':"
git grep -n -E "freeTrialEndsAt.*Unix timestamp" || echo "no matches"
fi
Length of output: 6599
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== commerce.ts (lines 140-190) ==="
sed -n '140,190p' packages/types/src/commerce.ts || true
echo -e "\n=== commerce.ts (lines 740-780) ==="
sed -n '740,780p' packages/types/src/commerce.ts || true
echo -e "\n=== json.ts (lines 720-820) ==="
sed -n '720,820p' packages/types/src/json.ts || true
echo -e "\n=== clerk-js CommerceStatement.ts (lines 1-120) ==="
sed -n '1,120p' packages/clerk-js/src/core/resources/CommerceStatement.ts || true
echo -e "\n=== Plans.tsx around TODOs (lines 1-240) ==="
sed -n '1,240p' packages/clerk-js/src/ui/contexts/components/Plans.tsx || true
echo -e "\n=== CommerceSubscription.ts around TODO (lines 1-120) ==="
sed -n '1,120p' packages/clerk-js/src/core/resources/CommerceSubscription.ts || true
echo -e "\n=== rspack.config.js around TODO (lines 1-140) ==="
sed -n '1,140p' packages/clerk-js/rspack.config.js || true
Length of output: 25897
Critical: add missing BillingStatementGroup.id; audit Exclude<'orgId'> uses; remove @Commerce TODOs
- BillingStatementGroup is missing an id field in the types: add
id: string
to packages/types/src/json.ts (interface BillingStatementGroupJSON) and packages/types/src/commerce.ts (interface BillingStatementGroup). clerk-js class BillingStatementGroup (packages/clerk-js/src/core/resources/CommerceStatement.ts) expectsdata.id
. - Exclude<..., 'orgId'> occurrences found in packages/types/src/commerce.ts for methods initializePaymentSource, addPaymentSource, getPaymentSources — confirm removal of orgId is intentional (or replace with clearer Omit<> usage).
- Leftover @Commerce TODOs to clean up in: packages/types/src/json.ts, packages/types/src/commerce.ts, packages/clerk-js/src/ui/contexts/components/Plans.tsx, packages/clerk-js/src/core/resources/CommerceSubscription.ts, packages/clerk-js/rspack.config.js.
- freeTrialEndsAt
'Unix timestamp'
doc check: no matches found.
🤖 Prompt for AI Agents
In packages/types/src/commerce.ts around lines 156-159 and related types, add
the missing id: string to the BillingStatementGroup interface (and add the
corresponding id: string to packages/types/src/json.ts interface
BillingStatementGroupJSON) because clerk-js expects data.id; audit every
occurrence of Exclude<..., 'orgId'> in packages/types/src/commerce.ts
(initializePaymentSource, addPaymentSource, getPaymentSources) and replace with
Omit<..., 'orgId'> or explicitly document/confirm that orgId must be omitted to
make intentions clear; remove the leftover @COMMERCE TODO comments across
packages/types/src/json.ts, packages/types/src/commerce.ts,
packages/clerk-js/src/ui/contexts/components/Plans.tsx,
packages/clerk-js/src/core/resources/CommerceSubscription.ts, and
packages/clerk-js/rspack.config.js so the codebase no longer contains stale
TODOs; run type checks and unit tests to ensure the added id fields and
signature changes don’t break usages (notably
packages/clerk-js/src/core/resources/CommerceStatement.ts which reads data.id).
packages/types/src/commerce.ts
Outdated
subscriptionItem: BillingSubscriptionItemResource; | ||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Should subscriptionItem be optional for checkout payments?
For chargeType = 'checkout' there may be no subscription item.
Run to find unchecked usages:
🏁 Script executed:
#!/bin/bash
rg -nP -C3 '\bsubscriptionItem\b' packages --type ts --type tsx
Length of output: 27
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for 'subscriptionItem' in packages (.ts/.tsx files)..."
rg -nP -C3 '\bsubscriptionItem\b' packages -g '**/*.ts' -g '**/*.tsx' || true
echo
echo "Printing context around lines 630-670 in packages/types/src/commerce.ts if it exists..."
if [ -f packages/types/src/commerce.ts ]; then
nl -ba packages/types/src/commerce.ts | sed -n '630,670p'
else
echo "packages/types/src/commerce.ts not found"
fi
Length of output: 14857
Make subscriptionItem optional and guard parsing/usage
chargeType='checkout' can omit subscription_item; current types and parsing assume it's always present and may throw at runtime.
- packages/types/src/commerce.ts (line ~651): change
subscriptionItem: BillingSubscriptionItemResource;
→ subscriptionItem?: BillingSubscriptionItemResource; - packages/clerk-js/src/core/resources/CommercePayment.ts (declaration ~line 22, constructor ~line 42): make the property optional and guard the assignment:
subscriptionItem?: BillingSubscriptionItemResource;
this.subscriptionItem = data.subscription_item ? new BillingSubscriptionItem(data.subscription_item) : undefined; - Audit UI code that dereferences subscriptionItem without checks (e.g. packages/clerk-js/src/ui/components/Statements/StatementPage.tsx — item.subscriptionItem.plan.name / related uses) and add conditional rendering or null-checks. PaymentAttemptPage already handles undefined.
🤖 Prompt for AI Agents
In packages/types/src/commerce.ts around lines 651-652, change subscriptionItem:
BillingSubscriptionItemResource; to subscriptionItem?:
BillingSubscriptionItemResource; and in
packages/clerk-js/src/core/resources/CommercePayment.ts (declaration around line
22 and constructor around line 42) make the property optional
(subscriptionItem?: BillingSubscriptionItemResource;) and guard the assignment
so you only construct BillingSubscriptionItem when data.subscription_item is
present (assign undefined otherwise). Also update UI usages that directly
dereference subscriptionItem (e.g.
packages/clerk-js/src/ui/components/Statements/StatementPage.tsx where code
accesses item.subscriptionItem.plan.name) to add null/undefined checks or
conditional rendering before accessing plan or name; PaymentAttemptPage already
handles undefined so leave it unchanged.
packages/types/src/commerce.ts
Outdated
* The `BillingStatementGroup` type represents a group of payment items within a statement. | ||
* | ||
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to pin the SDK version and the clerk-js version to avoid breaking changes. | ||
*/ | ||
export interface CommerceStatementGroup { | ||
export interface BillingStatementGroup { | ||
/** | ||
* The date and time when this group of payment items was created or last updated. | ||
*/ | ||
timestamp: Date; | ||
/** | ||
* An array of payment resources that belong to this group. | ||
*/ | ||
items: CommercePaymentResource[]; | ||
items: BillingPaymentResource[]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add id to BillingStatementGroup to match runtime shape.
clerk-js BillingStatementGroup includes id; types here omit it.
export interface BillingStatementGroup {
+ /**
+ * Stable identifier of the group.
+ */
+ id: string;
/**
* The date and time when this group of payment items was created or last updated.
*/
timestamp: Date;
📝 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.
* The `BillingStatementGroup` type represents a group of payment items within a statement. | |
* | |
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to pin the SDK version and the clerk-js version to avoid breaking changes. | |
*/ | |
export interface CommerceStatementGroup { | |
export interface BillingStatementGroup { | |
/** | |
* The date and time when this group of payment items was created or last updated. | |
*/ | |
timestamp: Date; | |
/** | |
* An array of payment resources that belong to this group. | |
*/ | |
items: CommercePaymentResource[]; | |
items: BillingPaymentResource[]; | |
} | |
* The `BillingStatementGroup` type represents a group of payment items within a statement. | |
* | |
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to pin the SDK version and the clerk-js version to avoid breaking changes. | |
*/ | |
export interface BillingStatementGroup { | |
/** | |
* Stable identifier of the group. | |
*/ | |
id: string; | |
/** | |
* The date and time when this group of payment items was created or last updated. | |
*/ | |
timestamp: Date; | |
/** | |
* An array of payment resources that belong to this group. | |
*/ | |
items: BillingPaymentResource[]; | |
} |
🤖 Prompt for AI Agents
In packages/types/src/commerce.ts around lines 752 to 765, the
BillingStatementGroup interface is missing the runtime id property; add an id:
string field to the interface (alongside timestamp and items) so the TypeScript
type matches the runtime shape returned by clerk-js.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/react/src/components/__tests__/PlanDetailsButton.test.tsx (3)
3-3
: Remove redundant type import (will be unused after dropping the cast).
BillingPayerResourceType
isn’t needed if you rely on the string literal type forforPayerType
.-import type { BillingPayerResourceType, BillingPlanResource, Theme } from '@clerk/types'; +import type { BillingPlanResource, Theme } from '@clerk/types';
56-56
: Drop unnecessary cast on forPayerType.The literal
'user'
already narrows to the union; the cast is noise and can mask type drift.- forPayerType: 'user' as BillingPayerResourceType, + forPayerType: 'user',
31-65
: Optional: Usesatisfies
to keep literals narrow and avoid widening.This preserves literal types in the mock while still validating the shape against
BillingPlanResource
.-const mockPlanResource: BillingPlanResource = { +const mockPlanResource = { id: 'plan_123', name: 'Test Plan', fee: { amount: 1000, amountFormatted: '10.00', currencySymbol: '$', currency: 'USD', }, annualMonthlyFee: { amount: 833, amountFormatted: '8.33', currencySymbol: '$', currency: 'USD', }, annualFee: { amount: 10000, amountFormatted: '100.00', currencySymbol: '$', currency: 'USD', }, description: 'Test Plan Description', hasBaseFee: true, isRecurring: true, isDefault: false, - forPayerType: 'user', + forPayerType: 'user', publiclyVisible: true, slug: 'test-plan', avatarUrl: 'https://example.com/avatar.png', freeTrialDays: 0, freeTrialEnabled: false, features: [], pathRoot: '', reload: vi.fn(), -}; +} satisfies BillingPlanResource;
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
.changeset/yummy-snails-work.md
(1 hunks)packages/react/src/components/__tests__/PlanDetailsButton.test.tsx
(3 hunks)packages/react/src/isomorphicClerk.ts
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- .changeset/yummy-snails-work.md
🧰 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/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.test.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/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.test.tsx
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.test.tsx
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.test.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
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
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
Useconst assertions
for literal types:as const
Usesatisfies
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 ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
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/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.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/react/src/isomorphicClerk.ts
packages/react/src/components/__tests__/PlanDetailsButton.test.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/react/src/components/__tests__/PlanDetailsButton.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/react/src/components/__tests__/PlanDetailsButton.test.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/react/src/components/__tests__/PlanDetailsButton.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/react/src/components/__tests__/PlanDetailsButton.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/react/src/components/__tests__/PlanDetailsButton.test.tsx
🧬 Code graph analysis (2)
packages/react/src/isomorphicClerk.ts (1)
packages/types/src/commerce.ts (1)
BillingNamespace
(20-100)
packages/react/src/components/__tests__/PlanDetailsButton.test.tsx (1)
packages/types/src/commerce.ts (2)
BillingPlanResource
(209-351)BillingPayerResourceType
(110-110)
🔇 Additional comments (1)
packages/react/src/isomorphicClerk.ts (1)
19-19
: Type rename import looks correct — confirm no lingering Commerce usages or add compat alias*
- packages/types/src/commerce.ts:20 exports interface BillingNamespace.
- No compat alias found (e.g. export type CommerceBillingNamespace = BillingNamespace).
- rg reported "unrecognized file type: tsx" — the prior search may be incomplete; re-run a broad search (eg. rg -nP '\bCommerce[A-Z]\w+\b' -S) to verify no remaining Commerce* identifiers.
- Action: either add the compat alias in packages/types/src/commerce.ts or confirm all Commerce* usages were migrated and removed.
client: ClientResource | undefined; | ||
billing: CommerceBillingNamespace | undefined; | ||
billing: BillingNamespace | undefined; | ||
apiKeys: APIKeysNamespace | undefined; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSDoc for public billing
property (meets project guidelines and signals beta status)
Per guidelines, public APIs must have JSDoc. Recommend documenting the experimental nature here.
client: ClientResource | undefined;
- billing: BillingNamespace | undefined;
+ /**
+ * @experimental Billing is in public beta and its API may change without notice.
+ * Consider pinning clerk-js and SDK versions to avoid unexpected breakage.
+ */
+ billing: BillingNamespace | undefined;
apiKeys: APIKeysNamespace | undefined;
📝 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.
client: ClientResource | undefined; | |
billing: CommerceBillingNamespace | undefined; | |
billing: BillingNamespace | undefined; | |
apiKeys: APIKeysNamespace | undefined; | |
}; | |
client: ClientResource | undefined; | |
/** | |
* @experimental Billing is in public beta and its API may change without notice. | |
* Consider pinning clerk-js and SDK versions to avoid unexpected breakage. | |
*/ | |
billing: BillingNamespace | undefined; | |
apiKeys: APIKeysNamespace | undefined; | |
}; |
🤖 Prompt for AI Agents
In packages/react/src/isomorphicClerk.ts around lines 112 to 115, the public
property "billing" lacks JSDoc; add a JSDoc comment above the billing
declaration that documents the property, marks it as public and
experimental/beta (e.g., "@public" and "@experimental" or "Beta" in the
description), and briefly describes its purpose/shape to meet project guidelines
for public APIs.
get billing(): BillingNamespace | undefined { | ||
return this.clerkjs?.billing; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSDoc for billing
getter (public API) with @experimental note
Keeps runtime API docs consistent with @clerk/types
and repo standards.
- get billing(): BillingNamespace | undefined {
+ /**
+ * Billing namespace for client-side operations.
+ * @experimental The Billing feature is in public beta and may change.
+ */
+ get billing(): BillingNamespace | undefined {
return this.clerkjs?.billing;
}
📝 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.
get billing(): BillingNamespace | undefined { | |
return this.clerkjs?.billing; | |
} | |
/** | |
* Billing namespace for client-side operations. | |
* @experimental The Billing feature is in public beta and may change. | |
*/ | |
get billing(): BillingNamespace | undefined { | |
return this.clerkjs?.billing; | |
} |
🤖 Prompt for AI Agents
In packages/react/src/isomorphicClerk.ts around lines 725 to 727, add a JSDoc
block above the billing getter describing the getter, its return type
(BillingNamespace | undefined), and mark it with an @experimental tag to match
@clerk/types and repo standards; keep the description concise (what it returns
and when it can be undefined) and include the @returns annotation, ensuring
formatting and wording are consistent with other public API JSDoc comments in
the repository.
Description
Renames
Commerce****
->Billing****
for@clerk/types
and@clerk/clerk-js
.In follow up PRs we will address@clerk/backend
and localization keysChecklist
pnpm test
runs as expected.pnpm build
runs as expected.Type of change
Summary by CodeRabbit
New Features
Refactor
Chores