Skip to content

Conversation

@panteliselef
Copy link
Contributor

@panteliselef panteliselef commented Aug 25, 2025

Description

Adds serialization for CommerceSubscription and updates CommerceSubscriptionItem in order to add getOrganizationBillingSubscription to BillingApi.

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
    • Experimental endpoint to fetch an organization’s billing subscription.
    • New CommerceSubscription type: status, payer info, lifecycle timestamps (created/updated/active/past-due/ended), subscription items, and next-payment details (date, amount, currency).
    • Subscription item model expanded with payer, created/updated/ended timestamps, required period end, nullable cancel/past-due fields, and optional free-trial flag.
    • Webhook/event payloads updated with payer and expanded timestamps.
    • New subscription types exported in the public API.

@changeset-bot
Copy link

changeset-bot bot commented Aug 25, 2025

🦋 Changeset detected

Latest commit: f6443b8

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

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

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

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

@vercel
Copy link

vercel bot commented Aug 25, 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 Aug 26, 2025 8:52am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds an experimental Billing API method to fetch an organization’s billing subscription, introduces a new CommerceSubscription resource and JSON types, extends CommerceSubscriptionItem fields/timestamps, updates deserialization to support CommerceSubscription, and re-exports the new types.

Changes

Cohort / File(s) Summary of changes
API endpoint: organization subscription
packages/backend/src/api/endpoints/BillingApi.ts
Adds experimental method getOrganizationBillingSubscription(organizationId: string) which validates the ID, issues GET /organizations/{organizationId}/billing/subscription, and returns a CommerceSubscription. Introduces organizationBasePath.
Resource model: CommerceSubscription
packages/backend/src/api/resources/CommerceSubscription.ts
Adds experimental CommerceSubscription class with constructor and static fromJSON, mapping id, status, payerId, createdAt/updatedAt, activeAt/pastDueAt, subscriptionItems, nextPayment (date + amount), and eligibleForFreeTrial.
Resource model updates: CommerceSubscriptionItem + JSON
packages/backend/src/api/resources/CommerceSubscriptionItem.ts, packages/backend/src/api/resources/JSON.ts
Extends CommerceSubscriptionItem with createdAt, updatedAt, endedAt, payerId, optional isFreeTrial; makes timestamp fields explicit nullable (period_end, canceled_at, past_due_at); changes next_payment to include date; adds CommerceSubscriptionJSON and adjusts webhook JSON types.
Deserializer support
packages/backend/src/api/resources/Deserializer.ts
Adds case to deserialize ObjectType.CommerceSubscription via CommerceSubscription.fromJSON.
Public exports / index updates
packages/backend/src/api/resources/index.ts, packages/backend/src/index.ts
Re-exports CommerceSubscription and CommerceSubscriptionJSON from central package indexes, expanding the public API surface.
Release metadata
.changeset/fresh-clubs-float.md
Adds changeset documenting the new getOrganizationBillingSubscription API addition.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant BillingAPI
  participant HTTP as "HTTP Client"
  participant Deserializer

  Client->>BillingAPI: getOrganizationBillingSubscription(orgId)
  BillingAPI->>HTTP: GET /organizations/{orgId}/billing/subscription
  HTTP-->>BillingAPI: 200 CommerceSubscriptionJSON
  BillingAPI->>Deserializer: CommerceSubscription.fromJSON(JSON)
  Deserializer-->>BillingAPI: CommerceSubscription
  BillingAPI-->>Client: CommerceSubscription
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A hop, a ping, a subscription found,
JSON carrots scattered on the ground.
I parse each field with whiskered cheer,
stitch items, dates, and payer near.
Org billing fetched — a rabbit’s bound 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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/bapi-fetch-org-subscription

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 25, 2025

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/elements

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

@clerk/clerk-expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/clerk-react

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

@clerk/react-router

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

@clerk/remix

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/themes

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

@clerk/types

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

@clerk/upgrade

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

@clerk/vue

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

commit: f6443b8

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

🧹 Nitpick comments (9)
packages/backend/src/api/endpoints/BillingApi.ts (1)

11-11: Localize organizations base path

Defining organizationBasePath here is a clear improvement for readability and maintainability. At the moment, there isn’t a shared constants module for API paths elsewhere in the codebase. As an optional enhancement, you could:

  • Create a new file (e.g. packages/backend/src/api/constants.ts)
    • Export shared base paths such as:
    export const COMMERCE_BASE_PATH = '/commerce';
    export const ORGANIZATIONS_BASE_PATH = '/organizations';
  • Update BillingApi.ts to import and use these constants instead of declaring them inline.

This change isn’t required now, but if you find the same paths used in multiple places (or expect them to grow), centralizing them will help avoid drift.

packages/backend/src/api/resources/CommerceSubscription.ts (2)

42-46: Prefer immutable collection typing for subscriptionItems

Expose as ReadonlyArray to prevent accidental mutation by consumers.

Apply this diff:

-    readonly subscriptionItems: CommerceSubscriptionItem[],
+    readonly subscriptionItems: ReadonlyArray<CommerceSubscriptionItem>,

53-66: Deduplicate money amount mapping (optional)

The nextPayment.amount mapping mirrors logic in CommerceSubscriptionItem. Consider a tiny local helper to keep things DRY and uniform.

   static fromJSON(data: CommerceSubscriptionJSON): CommerceSubscription {
+    const toMoney = (a: { amount: number; amount_formatted: string; currency: string; currency_symbol: string }) => ({
+      amount: a.amount,
+      amountFormatted: a.amount_formatted,
+      currency: a.currency,
+      currencySymbol: a.currency_symbol,
+    });
     const nextPayment = data.next_payment
       ? {
           date: data.next_payment.date,
-          amount: {
-            amount: data.next_payment.amount.amount,
-            amountFormatted: data.next_payment.amount.amount_formatted,
-            currency: data.next_payment.amount.currency,
-            currencySymbol: data.next_payment.amount.currency_symbol,
-          },
+          amount: toMoney(data.next_payment.amount),
         }
       : null;
packages/backend/src/api/resources/CommerceSubscriptionItem.ts (4)

47-55: Clarify timestamp units across newly added fields

Please state whether these timestamps are seconds or milliseconds since epoch to avoid consumer confusion and downstream bugs.

Apply this diff to each timestamp JSDoc (example shown for createdAt/updatedAt; mirror for periodEnd, canceledAt, pastDueAt, endedAt):

     /**
-     * The date and time the subscription item was created.
+     * The date and time the subscription item was created (Unix timestamp in milliseconds).
      */
     readonly createdAt: number,
     /**
-     * The date and time the subscription item was last updated.
+     * The date and time the subscription item was last updated (Unix timestamp in milliseconds).
      */
     readonly updatedAt: number,

Also applies to: 58-58, 62-66, 70-74


80-83: Align lifetimePaid optionality with JSON contract

JSON declares lifetime_paid as always present (object), whereas the instance property is optional. Consider making it non-optional to reflect the wire contract and simplify consumer null checks.

Apply this diff:

-    readonly lifetimePaid?: CommerceMoneyAmount | null,
+    readonly lifetimePaid: CommerceMoneyAmount | null,

1-1: Avoid cross-package type drift for money JSON

This file mixes CommerceMoneyAmountJSON from @clerk/types with local JSON contracts. To prevent drift, export CommerceMoneyAmountJSON from ./JSON and import it here.

Apply these diffs in both files:

  1. In packages/backend/src/api/resources/JSON.ts export the interface (see separate comment on that file for exact diff).

  2. Update the import here:

-import type { CommerceMoneyAmountJSON } from '@clerk/types';
+import type { CommerceMoneyAmountJSON } from './JSON';

85-120: Add serialization tests for new fields

Please add unit tests covering: createdAt/updatedAt mapping, nullability for periodEnd/canceledAt/pastDueAt/endedAt, payerId propagation, and isFreeTrial passthrough.

I can scaffold tests in packages/backend/src/api/resources/__tests__/CommerceSubscriptionItem.test.ts that construct JSON payloads and assert the instance fields. Want me to generate them?

packages/backend/src/api/resources/JSON.ts (2)

803-809: Export CommerceMoneyAmountJSON to keep money types consistent across backend resources

CommerceSubscriptionItem.ts needs a local, single source of truth for money JSON. Exporting this interface allows importing from ./JSON instead of @clerk/types.

Apply this diff:

-interface CommerceMoneyAmountJSON {
+export interface CommerceMoneyAmountJSON {
   amount: number;
   amount_formatted: string;
   currency: string;
   currency_symbol: string;
 }

860-873: Item JSON: move to explicit nulls is good; add concise field docs

Switching to explicit null is clear. Please add short comments for new fields (payer_id, ended_at, created_at/updated_at, canceled_at/past_due_at, is_free_trial) to guide integrators.

Example (apply similarly to others):

 export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
   object: typeof ObjectType.CommerceSubscriptionItem;
   status: CommerceSubscriptionItemStatus;
   plan_period: 'month' | 'annual';
-  payer_id: string;
+  /** The associated payer ID for this item. */
+  payer_id: string;
   period_start: number;
-  period_end: number | null;
+  /** Null for items on the free plan. */
+  period_end: number | null;
-  is_free_trial?: boolean;
+  /** Temporary flag during beta; may be removed after GA. */
+  is_free_trial?: boolean;
-  ended_at: number | null;
+  /** Timestamp when the item ended, or null if active. */
+  ended_at: number | null;
-  created_at: number;
-  updated_at: number;
+  /** Creation timestamp. */
+  created_at: number;
+  /** Last update timestamp. */
+  updated_at: number;
-  canceled_at: number | null;
-  past_due_at: number | null;
+  /** When cancellation took effect, if applicable. */
+  canceled_at: number | null;
+  /** When the item became past due, if applicable. */
+  past_due_at: number | null;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 453cf86 and 08ef122.

📒 Files selected for processing (7)
  • packages/backend/src/api/endpoints/BillingApi.ts (2 hunks)
  • packages/backend/src/api/resources/CommerceSubscription.ts (1 hunks)
  • packages/backend/src/api/resources/CommerceSubscriptionItem.ts (2 hunks)
  • packages/backend/src/api/resources/Deserializer.ts (2 hunks)
  • packages/backend/src/api/resources/JSON.ts (2 hunks)
  • packages/backend/src/api/resources/index.ts (1 hunks)
  • packages/backend/src/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{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/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.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/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.ts
packages/**/*.{ts,tsx,d.ts}

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

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.ts
packages/**/index.{js,ts}

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

Use tree-shaking friendly exports

Files:

  • packages/backend/src/index.ts
  • packages/backend/src/api/resources/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/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.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/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
  • packages/backend/src/api/resources/Deserializer.ts
  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/endpoints/BillingApi.ts
  • packages/backend/src/api/resources/JSON.ts
  • packages/backend/src/api/resources/CommerceSubscriptionItem.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/backend/src/index.ts
  • packages/backend/src/api/resources/index.ts
🧬 Code graph analysis (4)
packages/backend/src/api/resources/Deserializer.ts (3)
packages/backend/src/api/resources/JSON.ts (2)
  • ObjectType (19-74)
  • ObjectType (76-76)
packages/backend/src/api/resources/CommerceSubscription.ts (1)
  • CommerceSubscription (9-79)
packages/backend/src/index.ts (1)
  • CommerceSubscription (148-148)
packages/backend/src/api/resources/CommerceSubscription.ts (2)
packages/backend/src/api/resources/JSON.ts (1)
  • CommerceSubscriptionJSON (980-994)
packages/backend/src/api/resources/CommerceSubscriptionItem.ts (1)
  • CommerceSubscriptionItem (10-121)
packages/backend/src/api/endpoints/BillingApi.ts (2)
packages/backend/src/api/resources/CommerceSubscription.ts (1)
  • CommerceSubscription (9-79)
packages/backend/src/index.ts (1)
  • CommerceSubscription (148-148)
packages/backend/src/api/resources/JSON.ts (1)
packages/types/src/json.ts (4)
  • CommerceSubscriptionJSON (804-824)
  • ClerkResourceJSON (36-40)
  • CommerceSubscriptionItemJSON (773-794)
  • CommerceMoneyAmountJSON (834-839)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (8)
packages/backend/src/api/resources/Deserializer.ts (2)

41-41: Importing CommerceSubscription at runtime is correct

Needed as a runtime value to call fromJSON; type-only import would break deserialization.


188-191: Coverage Confirmed: CommerceSubscription Deserialization

The switch in Deserializer.ts now handles ObjectType.CommerceSubscription (line 188) and ObjectType.CommerceSubscriptionItem (line 190) exactly once. The corresponding JSON mappings for 'commerce_subscription' and 'commerce_subscription_item' are present in JSON.ts (lines 70–71). All object types are fully supported—ready to merge.

packages/backend/src/api/resources/index.ts (1)

61-61: Expose CommerceSubscription via resources barrel

Consistent with existing pattern; keeps public surface coherent for the new resource.

packages/backend/src/index.ts (2)

104-106: Export CommerceSubscriptionJSON type

Good addition; keeps JSON surface in sync with new resource.


148-150: Export CommerceSubscription as a type-only export

Matches existing convention (resources exposed as types from the root), avoiding unintended runtime exports. LGTM.

packages/backend/src/api/endpoints/BillingApi.ts (1)

5-5: Type-only import for CommerceSubscription is appropriate

Prevents pulling the class value into the bundle while preserving type safety on the method signature.

packages/backend/src/api/resources/CommerceSubscription.ts (1)

1-79: Solid resource model and JSON mapping

Constructor shape, field nullability, and fromJSON mapping (including money amount normalization and nested items) look correct and consistent with CommerceSubscriptionItem.

packages/backend/src/api/resources/CommerceSubscriptionItem.ts (1)

85-99: Item-level nextPayment is correctly defined and used

  • The CommerceSubscriptionItem constructor expects nextPayment as { amount: number; date: number } | null, which exactly matches the item-level JSON schema in JSON.ts (lines 874–876).
  • A search across the codebase found no places where nextPayment.amount is treated as an object (e.g. accessing amountFormatted), so there are no erroneous call sites.
  • The asymmetry—item level using a raw number for amount, and subscription level using a full CommerceMoneyAmountJSON—is intentional and handled correctly in each fromJSON method.

Comment on lines +51 to +61
/**
* @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 to avoid breaking changes.
*/
public async getOrganizationBillingSubscription(organizationId: string) {
this.requireId(organizationId);
return this.request<CommerceSubscription>({
method: 'GET',
path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add explicit return type and complete JSDoc for the public API

Per guidelines, public APIs should declare explicit return types and comprehensive JSDoc.

Apply this diff within the method block:

   /**
    * @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 to avoid breaking changes.
+   * @param organizationId - The target organization ID (e.g. "org_...").
+   * @returns The organization's billing subscription resource.
+   * @throws ClerkAPIResponseError If the organization does not exist or billing is not enabled.
    */
-  public async getOrganizationBillingSubscription(organizationId: string) {
+  public async getOrganizationBillingSubscription(organizationId: string): Promise<CommerceSubscription> {
     this.requireId(organizationId);
     return this.request<CommerceSubscription>({
       method: 'GET',
       path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
     });
   }

I can add an integration test stub that mocks AbstractAPI.request to verify the shape and deserialization path—want me to push that?


🏁 Script executed:

#!/bin/bash
# Verify that the new method compiles to a Promise<CommerceSubscription> (textual check)
rg -nP "public\s+async\s+getOrganizationBillingSubscription\([^)]*\)\s*:\s*Promise<CommerceSubscription>" packages/backend/src/api/endpoints/BillingApi.ts -n

# Ensure the endpoint path matches server route conventions elsewhere in the repo
rg -n "billing.*subscription" -C2 packages | sed -n '1,200p'

Length of output: 11395


Add explicit return type and complete JSDoc for getOrganizationBillingSubscription
Public SDK methods must declare explicit return types and comprehensive JSDoc for parameters, return values, and thrown errors per our API guidelines.

– In packages/backend/src/api/endpoints/BillingApi.ts, update the method signature:

   /**
    * @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 to avoid breaking changes.
+   * @param organizationId – The target organization’s ID (e.g. "org_…").
+   * @returns The organization’s billing subscription resource.
+   * @throws ClerkAPIResponseError If the organization does not exist or billing is not enabled.
    */
-  public async getOrganizationBillingSubscription(organizationId: string) {
+  public async getOrganizationBillingSubscription(
+    organizationId: string
+  ): Promise<CommerceSubscription> {
     this.requireId(organizationId);
     return this.request<CommerceSubscription>({
       method: 'GET',
       path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
     });
   }

• Please also verify that the billing/subscription endpoint path aligns exactly with our server’s API specification—​I wasn’t able to locate a matching route definition in the repo.
• Let me know if you’d like me to add an integration‐test stub that mocks AbstractAPI.request to validate the response shape and deserialization path.

📝 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
/**
* @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 to avoid breaking changes.
*/
public async getOrganizationBillingSubscription(organizationId: string) {
this.requireId(organizationId);
return this.request<CommerceSubscription>({
method: 'GET',
path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
});
}
/**
* @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 to avoid breaking changes.
* @param organizationId The target organization’s ID (e.g. "org_…").
* @returns The organization’s billing subscription resource.
* @throws ClerkAPIResponseError If the organization does not exist or billing is not enabled.
*/
public async getOrganizationBillingSubscription(
organizationId: string
): Promise<CommerceSubscription> {
this.requireId(organizationId);
return this.request<CommerceSubscription>({
method: 'GET',
path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
});
}
🤖 Prompt for AI Agents
In packages/backend/src/api/endpoints/BillingApi.ts around lines 51-61, the
public method getOrganizationBillingSubscription lacks an explicit return type
and complete JSDoc; update the method signature to explicitly return
Promise<CommerceSubscription> and add a JSDoc block that documents the
organizationId parameter, the Promise<CommerceSubscription> return value, and
any errors that can be thrown (e.g., validation errors from requireId and
network/request errors from AbstractAPI.request). While changing the
signature/JSDoc, verify the endpoint path "billing/subscription" against the
server route definitions (search the repo for matching routes) and correct the
joinPaths call if the server uses a different path (e.g.,
"billing/subscriptions" or a nested route); if you change the path, mirror that
in the JSDoc. Finally, run unit/integration tests or add a small test stub that
mocks AbstractAPI.request to assert the response deserializes to
CommerceSubscription.

items: CommerceSubscriptionItemWebhookEventJSON[];
}

export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
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

Add comprehensive JSDoc for the new public API

Per repo guidelines, all public APIs need JSDoc. Add the experimental notice and field-level docs for payer_id, subscription_items, and next_payment.

Apply this diff:

- export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
+ /**
+  * @experimental This is an experimental API for the Billing feature that is available under a public beta,
+  * and the API is subject to change. Pin the SDK version to avoid breaking changes.
+  *
+  * Data model returned by `BillingApi.getOrganizationBillingSubscription`.
+  * - `subscription_items`: list of items in this subscription.
+  * - `next_payment`: next scheduled charge for the subscription, or null if none.
+  */
+ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
📝 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
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
/**
* @experimental This is an experimental API for the Billing feature that is available under a public beta,
* and the API is subject to change. Pin the SDK version to avoid breaking changes.
*
* Data model returned by `BillingApi.getOrganizationBillingSubscription`.
* - `subscription_items`: list of items in this subscription.
* - `next_payment`: next scheduled charge for the subscription, or null if none.
*/
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
🤖 Prompt for AI Agents
In packages/backend/src/api/resources/JSON.ts around line 980, add a
comprehensive JSDoc comment immediately above the exported interface
CommerceSubscriptionJSON: mark the API as experimental, document the interface
purpose, and add field-level @property tags describing payer_id (the unique
identifier for the payer, format and nullable/optional status),
subscription_items (array shape, item type and meaning), and next_payment (next
payment date/time or object shape and nullable/optional status). Ensure the
JSDoc follows repo conventions, uses @experimental or an explicit experimental
notice, and documents types/optionality for each field so the public API is
fully documented.

Comment on lines 980 to 995
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
object: typeof ObjectType.CommerceSubscription;
status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
payer_id: string;
created_at: number;
updated_at: number;
active_at: number | null;
past_due_at: number | null;
subscription_items: CommerceSubscriptionItemJSON[];
next_payment: {
date: number;
amount: CommerceMoneyAmountJSON;
} | null;
eligible_for_free_trial?: boolean | null;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

New CommerceSubscriptionJSON: align with public typing and document the API

There are notable divergences from @clerk/types (status union breadth, subscription_items nullability, updated_at nullability, next_payment optionality/shape). Confirm the server contract and either (a) match the published types or (b) intentionally version/scope this as a backend-only contract to avoid consumer confusion. Also, add JSDoc as this is exported publicly by the backend package.

  1. If this should match @clerk/types, apply:
 export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   object: typeof ObjectType.CommerceSubscription;
-  status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
+  status: 'active' | 'past_due';
   payer_id: string;
   created_at: number;
-  updated_at: number;
-  active_at: number | null;
+  updated_at: number | null;
+  active_at: number;
   past_due_at: number | null;
-  subscription_items: CommerceSubscriptionItemJSON[];
-  next_payment: {
+  subscription_items: CommerceSubscriptionItemJSON[] | null;
+  next_payment?: {
     date: number;
     amount: CommerceMoneyAmountJSON;
   } | null;
   eligible_for_free_trial?: boolean | null;
 }
  1. If divergence is intentional for backend, keep the shape but add explicit documentation:
+/**
+ * @experimental This is an experimental Billing API shape returned by
+ * `BillingApi.getOrganizationBillingSubscription`.
+ * Notes:
+ * - `status` includes terminal states for internal workflows.
+ * - `subscription_items` is never `null`; an empty array indicates no items.
+ * - `next_payment` is `null` when there is no upcoming charge.
+ */
 export interface CommerceSubscriptionJSON extends ClerkResourceJSON {

Run this to find all consumers and confirm assumptions about the above fields:


🏁 Script executed:

#!/bin/bash
# Find all references and check assumptions about nullability and status narrowing
rg -nP -C2 --type=ts '\bCommerceSubscriptionJSON\b' packages

# Inspect uses of `subscription_items` expecting null
rg -nP -C2 --type=ts '\bsubscription_items\s*[!=]==?\s*null\b' packages

# Inspect status narrowing to only 'active' | 'past_due'
rg -nP -C2 --type=ts "\.status\b" packages | sed -n '1,200p'

Length of output: 19263


🏁 Script executed:

#!/bin/bash
# Print published types CommerceSubscriptionJSON shape from @clerk/types around interface definition
sed -n '780,840p' packages/types/src/json.ts

Length of output: 2039


Align or Scope CommerceSubscriptionJSON with Public SDK Types

The CommerceSubscriptionJSON interface in packages/backend/src/api/resources/JSON.ts diverges significantly from the published SDK shape in @clerk/types. To avoid client confusion, please choose one of the following:

Match the published SDK types
Update the backend JSON shape so it exactly mirrors packages/types/src/json.ts’s CommerceSubscriptionJSON. For example:

export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
  object: typeof ObjectType.CommerceSubscription;
-   status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
+   status: Extract<CommerceSubscriptionStatus, 'active' | 'past_due'>;
-   payer_id: string;
+   payment_source_id: string;
  created_at: number;
-   updated_at: number;
-   active_at: number | null;
+   active_at: number;
+   updated_at: number | null;
  past_due_at: number | null;
-   subscription_items: CommerceSubscriptionItemJSON[];
-   next_payment: {
-     date: number;
-     amount: CommerceMoneyAmountJSON;
-   } | null;
-   eligible_for_free_trial?: boolean | null;
+   subscription_items: CommerceSubscriptionItemJSON[] | null;
+   next_payment?: { date: number; amount: CommerceMoneyAmountJSON };
+   eligible_for_free_trial?: boolean;
}

• Ensures the backend-generated JSON matches the client’s expectations.
• Removes internal-only statuses and nullability mismatches.

Keep the extended backend contract but scope/document it
If the extra statuses (canceled, ended, etc.) and the current nullability choices are intentional for internal workflows:

  1. Move this interface out of the public resources JSON file or rename it to indicate “internal.”
  2. Add JSDoc calling out that it is a backend-only or experimental shape:
    /**
     * @internal Experimental backend shape for CommerceSubscription.
     * - Includes terminal statuses for internal workflows.
     * - `active_at` may be null until activation.
     * - `subscription_items` is always an array.
     * - `next_payment` is null if no upcoming charge.
     */
    export interface InternalCommerceSubscriptionJSON {  }
  3. Expose only the aligned SDK shape in the public API layer.

Mandatory refactors are required here to maintain a consistent public contract. Please pick one approach and update the code accordingly.

🤖 Prompt for AI Agents
In packages/backend/src/api/resources/JSON.ts around lines 980-995, the
CommerceSubscriptionJSON interface diverges from the published @clerk/types SDK
shape (extra statuses and different nullability); either 1) change this
interface to exactly match packages/types/src/json.ts CommerceSubscriptionJSON
(same fields, types, nullability) so backend JSON aligns with client
expectations, or 2) if the extended shape is required for internal workflows,
move/rename it (e.g., InternalCommerceSubscriptionJSON) out of the public
resources file, add a clear JSDoc @internal explaining the extra statuses and
nullability, and ensure the public API layer continues to export only the
SDK-aligned CommerceSubscriptionJSON type.

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

Caution

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

⚠️ Outside diff range comments (1)
.changeset/fresh-clubs-float.md (1)

1-6: Please include a bump for the clerk-js package in this changeset

This PR touches packages/clerk-js—you’ve modified src/core/clerk.ts, the debug transports under src/core/modules/debug/, and the utils/debug.ts helper—so the @clerk/clerk-js package needs its own version bump in the same changeset. The existing bump for @clerk/backend is correct, but without a bump for clerk-js, those changes won’t be published.

• Update .changeset/fresh-clubs-float.md front-matter to something like:

---
'@clerk/backend': minor
'@clerk/clerk-js': patch
---

• No other public packages (astro, fastify, vue) have API changes—they only had devDependency updates—so they don’t require bumps here.

🧹 Nitpick comments (2)
.changeset/fresh-clubs-float.md (2)

5-6: Make the changeset summary more informative (mention experimental status, signature, and return type)

Clarify what was added and how consumers can use it. Suggested replacement keeps it concise while capturing key details.

-Add `getOrganizationBillingSubscription` to BillingApi.
+feat(backend): add experimental BillingApi.getOrganizationBillingSubscription(organizationId)
+
+- Calls GET /organizations/{organizationId}/billing/subscription
+- Returns a CommerceSubscription (and re-exports its types)

6-6: Ensure file ends with a trailing newline

A missing trailing newline can trip some linters/formatters. Add one if it's not already present.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 08ef122 and 0e94cc6.

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

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

Automated releases must use Changesets.

Files:

  • .changeset/fresh-clubs-float.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome)
  • GitHub Check: Integration Tests (tanstack-react-router, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, 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
🔇 Additional comments (1)
.changeset/fresh-clubs-float.md (1)

1-3: Valid changeset front‑matter and semver choice (minor) look correct

  • Uses Changesets properly per repo guidelines for automated releases.
  • Adding a new public API method is a backward‑compatible feature; "minor" is appropriate.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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

♻️ Duplicate comments (1)
packages/backend/src/api/resources/JSON.ts (1)

980-980: Add comprehensive JSDoc for the new public API (duplicate of existing review)

There’s already a prior comment asking for experimental notice and field-level docs for CommerceSubscriptionJSON. Agree and reiterate: this is required before merge if this interface remains public.

🧹 Nitpick comments (2)
packages/backend/src/api/resources/CommerceSubscription.ts (2)

10-51: Nit: Make access modifiers explicit for public API fields

Repo guidelines prefer explicit visibility. These constructor parameter properties are public but not marked. Consider adding the public modifier for clarity in public APIs.

-  constructor(
+  constructor(
-    readonly id: string,
+    public readonly id: string,
-    readonly status: CommerceSubscriptionJSON['status'],
+    public readonly status: CommerceSubscriptionJSON['status'],
-    readonly payerId: string,
+    public readonly payerId: string,
-    readonly createdAt: number,
+    public readonly createdAt: number,
-    readonly updatedAt: number,
+    public readonly updatedAt: number,
-    readonly activeAt: number | null,
+    public readonly activeAt: number | null,
-    readonly pastDueAt: number | null,
+    public readonly pastDueAt: number | null,
-    readonly subscriptionItems: CommerceSubscriptionItem[],
+    public readonly subscriptionItems: CommerceSubscriptionItem[],
-    readonly nextPayment: { date: number; amount: CommerceMoneyAmount } | null,
+    public readonly nextPayment: { date: number; amount: CommerceMoneyAmount } | null,
-    readonly eligibleForFreeTrial: boolean,
+    public readonly eligibleForFreeTrial: boolean,
   ) {}

54-64: Deduplicate money-amount shaping via a shared helper

You manually shape CommerceMoneyAmount here, while CommerceSubscriptionItem.fromJSON already defines a format function. To avoid divergence and ease maintenance, extract a shared formatter and reuse it.

-    const nextPayment = data.next_payment
-      ? {
-          date: data.next_payment.date,
-          amount: {
-            amount: data.next_payment.amount.amount,
-            amountFormatted: data.next_payment.amount.amount_formatted,
-            currency: data.next_payment.amount.currency,
-            currencySymbol: data.next_payment.amount.currency_symbol,
-          },
-        }
-      : null;
+    const nextPayment = data.next_payment
+      ? {
+          date: data.next_payment.date,
+          amount: formatMoneyAmountJSON(data.next_payment.amount),
+        }
+      : null;

Outside selected lines (new tiny utility):

// packages/backend/src/api/resources/money.ts
import type { CommerceMoneyAmount } from './CommercePlan';

export function formatMoneyAmountJSON(amount?: {
  amount: number;
  amount_formatted: string;
  currency: string;
  currency_symbol: string;
} | null): CommerceMoneyAmount | null | undefined {
  if (amount == null) return amount as null | undefined;
  return {
    amount: amount.amount,
    amountFormatted: amount.amount_formatted,
    currency: amount.currency,
    currencySymbol: amount.currency_symbol,
  };
}

And import it here:

+import { formatMoneyAmountJSON } from './money';
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 3321c50 and f6443b8.

📒 Files selected for processing (2)
  • packages/backend/src/api/resources/CommerceSubscription.ts (1 hunks)
  • packages/backend/src/api/resources/JSON.ts (2 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/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.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/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.ts
packages/**/*.{ts,tsx}

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

TypeScript is required for all packages

Files:

  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.ts
packages/**/*.{ts,tsx,d.ts}

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

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.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/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.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/backend/src/api/resources/CommerceSubscription.ts
  • packages/backend/src/api/resources/JSON.ts
🧬 Code graph analysis (2)
packages/backend/src/api/resources/CommerceSubscription.ts (2)
packages/backend/src/api/resources/JSON.ts (1)
  • CommerceSubscriptionJSON (980-994)
packages/backend/src/api/resources/CommerceSubscriptionItem.ts (1)
  • CommerceSubscriptionItem (10-121)
packages/backend/src/api/resources/JSON.ts (1)
packages/types/src/json.ts (4)
  • CommerceSubscriptionJSON (804-824)
  • ClerkResourceJSON (36-40)
  • CommerceSubscriptionItemJSON (773-794)
  • CommerceMoneyAmountJSON (834-839)
⏰ 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 (3)
packages/backend/src/api/resources/CommerceSubscription.ts (2)

5-8: Good: Public API clearly marked experimental

The class-level JSDoc follows the guideline to mark public beta APIs as experimental and advises pinning versions. Keep this pattern consistent across the new BillingApi surface.


66-77: No action needed—internal JSON contract is stricter than public types

I’ve confirmed that the backend’s own CommerceSubscriptionJSON (in packages/backend/src/api/resources/JSON.ts) declares:

  • updated_at: number (always present)
  • subscription_items: CommerceSubscriptionItemJSON[] (never null)

So in CommerceSubscription.fromJSON, both

data.updated_at

and

data.subscription_items.map(...)

are safe under the internal API contract. The nullable definitions in packages/types/src/json.ts apply to the public client-side SDK types, not this backend mapping. You can disregard the original nullability concern here.

Likely an incorrect or invalid review comment.

packages/backend/src/api/resources/JSON.ts (1)

864-873: Please review backend JSON type alignment with the published SDK types

The CommerceSubscriptionItemJSON definition in packages/backend/src/api/resources/JSON.ts diverges from the shape in packages/types/src/json.ts, which risks type drift and downstream confusion. Key mismatches:

  • Field rename/omission
    • Backend: payer_id: string
    • SDK: payment_source_id: string
  • Additional timestamp fields
    • Backend adds updated_at, ended_at, past_due_at, canceled_at (all number | null) beyond the SDK’s minimal set.
  • Optionality differences
    • Backend: period_end: number | null (nullable but required)
    • SDK: period_end?: number \| null (optional & nullable)
  • New boolean flag
    • Backend: is_free_trial?: boolean (absent in SDK)
  • Monetary shape mismatch
    • Backend: lifetime_paid: CommerceMoneyAmountJSON
    • SDK: next_payment: { amount: number; currency: string } at the item level

Decide one of the following:

Keep SDK-aligned public contract:
– Rename payer_idpayment_source_id
– Remove or optionalize fields not present in packages/types/src/json.ts
– Match lifetime_paid/next_payment shapes exactly

Minimal patch example:

-  payer_id: string;
+  payment_source_id: string;

Expose an extended internal shape:
– Annotate CommerceSubscriptionItemJSON with @internal JSDoc
– Document each extra field’s purpose and data origin
– Avoid re-exporting this type in the public API

Please confirm which approach reflects intended design and update the code (or documentation) accordingly.

Comment on lines +980 to +994
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
object: typeof ObjectType.CommerceSubscription;
status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
payer_id: string;
created_at: number;
updated_at: number;
active_at: number | null;
past_due_at: number | null;
subscription_items: CommerceSubscriptionItemJSON[];
next_payment?: {
date: number;
amount: CommerceMoneyAmountJSON;
};
eligible_for_free_trial?: boolean;
}
Copy link
Contributor

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 contract mismatch with packages/types: high risk of consumer breakage

This CommerceSubscriptionJSON differs from packages/types/src/json.ts in multiple ways:

  • status union includes terminal/internal states; SDK restricts to 'active' | 'past_due'
  • payer_id exists here; SDK has no top-level payer_id
  • active_at is nullable here; SDK requires it
  • updated_at is non-null here; SDK allows null
  • subscription_items is non-null array here; SDK allows null
  • next_payment optional shape matches, fine

Two safe paths:

Option A — Align with SDK shape (preferred for a public surface):

 export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   object: typeof ObjectType.CommerceSubscription;
-  status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
-  payer_id: string;
+  status: 'active' | 'past_due';
   created_at: number;
-  updated_at: number;
-  active_at: number | null;
+  active_at: number;
+  updated_at: number | null;
   past_due_at: number | null;
-  subscription_items: CommerceSubscriptionItemJSON[];
+  subscription_items: CommerceSubscriptionItemJSON[] | null;
   next_payment?: {
     date: number;
     amount: CommerceMoneyAmountJSON;
   };
   eligible_for_free_trial?: boolean;
 }

Additionally, update CommerceSubscription.fromJSON to handle possibly-null subscription_items and nullable updated_at; and drop payerId unless it exists at another level.

Option B — Keep extended backend shape but scope it:

  • Rename to InternalCommerceSubscriptionJSON (or add @internal) and avoid exporting it via public index files.
  • Add a mapper that produces the SDK-aligned shape for anything exposed publicly.
  • Ensure BillingApi.getOrganizationBillingSubscription returns the class CommerceSubscription (stable surface), not this extended JSON.

Given the scope of impact, please choose one path before merging. I can provide a follow-up patch for either approach, including the mapper changes in CommerceSubscription.ts.


🏁 Script executed:

#!/bin/bash
set -e

# Locate json.ts in packages/types/src
json_file=$(fd -t f json.ts packages/types/src | head -n1)
echo "Using file: $json_file"

# Show the CommerceSubscriptionJSON interface and its key fields
rg -n 'interface CommerceSubscriptionJSON' -C10 "$json_file"
rg -n 'status' -C2 "$json_file"
rg -n 'payer_id' -C2 "$json_file"
rg -n 'active_at' -C2 "$json_file"
rg -n 'updated_at' -C2 "$json_file"
rg -n 'subscription_items' -C2 "$json_file"

Length of output: 4023


🏁 Script executed:

#!/bin/bash
set -e

types_file="packages/types/src/json.ts"
echo "SDK JSON File: $types_file"

# Extract the CommerceSubscriptionJSON block
start=$(rg -n "export interface CommerceSubscriptionJSON" -n "$types_file" | cut -d: -f1)
# Capture through the matching closing brace (assuming no nested braces inside)
relative_end=$(tail -n +$start "$types_file" | sed -n '1,/^}/p' | tail -n1 | grep -n '^}' | cut -d: -f1)
end=$((start + relative_end - 1))
echo "--- CommerceSubscriptionJSON Definition ---"
sed -n "${start},${end}p" "$types_file"

echo
echo "--- Key Property Definitions ---"
echo "status:"
rg -n "status:" -C2 "$types_file" | sed -n '1,5p'
echo
echo "updated_at:"
rg -n "updated_at:" -C2 "$types_file" | sed -n '1,5p'
echo
echo "subscription_items:"
rg -n "subscription_items:" -C2 "$types_file" | sed -n '1,5p'

Length of output: 1458


🏁 Script executed:

#!/bin/bash
set -e

# Print lines 970-1040 of the backend JSON file to inspect CommerceSubscriptionJSON
sed -n '970,1040p' packages/backend/src/api/resources/JSON.ts

Length of output: 857


Public JSON contract mismatch in CommerceSubscriptionJSON (packages/backend/src/api/resources/JSON.ts)

The CommerceSubscriptionJSON defined in the backend package diverges from the SDK’s CommerceSubscriptionJSON in packages/types/src/json.ts. Left unaddressed, these differences will break downstream consumers:

status
– Backend:
'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete'
– SDK:
Extract<CommerceSubscriptionStatus, 'active' | 'past_due'> (only 'active' or 'past_due')
payer_id
– Backend: payer_id: string present at top level
– SDK: no payer_id field
active_at
– Backend: active_at: number | null
– SDK: active_at: number (non-nullable)
updated_at
– Backend: updated_at: number (non-nullable)
– SDK: updated_at: number | null
subscription_items
– Backend: subscription_items: CommerceSubscriptionItemJSON[] (non-nullable)
– SDK: subscription_items: CommerceSubscriptionItemJSON[] | null

Two paths forward:

  1. Align backend JSON with SDK public shape (preferred for stability)
    – Restrict status to 'active' | 'past_due'
    – Remove top-level payer_id
    – Make active_at: number (non-nullable) and updated_at: number | null
    – Allow subscription_items: CommerceSubscriptionItemJSON[] | null
    – Update CommerceSubscription.fromJSON to handle nullable fields and drop payerId

  2. Scope extended backend shape as internal
    – Rename to InternalCommerceSubscriptionJSON (or mark @internal) and don’t re-export it publicly
    – Add a mapper in CommerceSubscription.ts that converts the internal JSON into the SDK’s public shape
    – Ensure public methods (e.g. BillingApi.getOrganizationBillingSubscription) return the public CommerceSubscription

Given the high risk of breaking changes, please choose one approach before merging. I can follow up with a patch for either path.

@panteliselef panteliselef merged commit ba7f3fd into main Aug 26, 2025
39 checks passed
@panteliselef panteliselef deleted the elef/bapi-fetch-org-subscription branch August 26, 2025 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants