From e853b50bd6a49fa8ef1fdaba451e3b2d88f957b8 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 31 Jul 2025 15:45:41 +0300 Subject: [PATCH 1/9] feat(backend): Add billing api and an endpoint for fetching plans --- .changeset/early-bats-shout.md | 5 + .../backend/src/api/endpoints/BillingApi.ts | 20 ++++ packages/backend/src/api/factory.ts | 2 + .../backend/src/api/resources/CommercePlan.ts | 112 ++++++++++++++++++ .../backend/src/api/resources/Deserializer.ts | 6 + packages/backend/src/api/resources/Feature.ts | 15 +++ packages/backend/src/api/resources/JSON.ts | 86 +++++++++----- packages/backend/src/index.ts | 6 - 8 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 .changeset/early-bats-shout.md create mode 100644 packages/backend/src/api/endpoints/BillingApi.ts create mode 100644 packages/backend/src/api/resources/CommercePlan.ts create mode 100644 packages/backend/src/api/resources/Feature.ts diff --git a/.changeset/early-bats-shout.md b/.changeset/early-bats-shout.md new file mode 100644 index 00000000000..31ac584f0cc --- /dev/null +++ b/.changeset/early-bats-shout.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +WIP diff --git a/packages/backend/src/api/endpoints/BillingApi.ts b/packages/backend/src/api/endpoints/BillingApi.ts new file mode 100644 index 00000000000..a82fac1920b --- /dev/null +++ b/packages/backend/src/api/endpoints/BillingApi.ts @@ -0,0 +1,20 @@ +import type { ClerkPaginationRequest } from '@clerk/types'; + +import { joinPaths } from '../../util/path'; +import type { CommercePlan } from '../resources/CommercePlan'; +import type { PaginatedResourceResponse } from '../resources/Deserializer'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/commerce'; + +type GetOrganizationListParams = ClerkPaginationRequest; + +export class BillingAPI extends AbstractAPI { + public async getPlanList(params?: GetOrganizationListParams) { + return this.request>({ + method: 'GET', + path: joinPaths(basePath, 'plans'), + queryParams: params, + }); + } +} diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 8c101a8d4cb..c093e615627 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -29,6 +29,7 @@ import { WaitlistEntryAPI, WebhookAPI, } from './endpoints'; +import { BillingAPI } from './endpoints/BillingApi'; import { buildRequest } from './request'; export type CreateBackendApiOptions = Parameters[0]; @@ -52,6 +53,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { ), betaFeatures: new BetaFeaturesAPI(request), blocklistIdentifiers: new BlocklistIdentifierAPI(request), + billing: new BillingAPI(request), clients: new ClientAPI(request), domains: new DomainAPI(request), emailAddresses: new EmailAddressAPI(request), diff --git a/packages/backend/src/api/resources/CommercePlan.ts b/packages/backend/src/api/resources/CommercePlan.ts new file mode 100644 index 00000000000..fd59665ebc3 --- /dev/null +++ b/packages/backend/src/api/resources/CommercePlan.ts @@ -0,0 +1,112 @@ +import { Feature } from './Feature'; +import type { CommercePlanJSON } from './JSON'; + +/** + * The Backend `Organization` object is similar to the [`Organization`](https://clerk.com/docs/references/javascript/organization) object as it holds information about an organization, as well as methods for managing it. However, the Backend `Organization` object is different in that it is used in the [Backend API](https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/ListOrganizations){{ target: '_blank' }} and is not directly accessible from the Frontend API. + */ + +type CommerceAmount = { + amount: number; + amountFormatted: string; + currency: string; + currencySymbol: string; +}; + +export class CommercePlan { + constructor( + /** + * The unique identifier for the organization. + */ + readonly id: string, + /** + * The name of the organization. + */ + readonly productId: string, + /** + * The URL-friendly identifier of the user's active organization. If supplied, it must be unique for the instance. + */ + readonly name: string, + /** + * Holds the organization's logo. Compatible with Clerk's [Image Optimization](https://clerk.com/docs/guides/image-optimization). + */ + readonly slug: string, + /** + * Whether the organization has an image. + */ + readonly description: string | undefined, + /** + * The date when the organization was first created. + */ + readonly isDefault: boolean, + /** + * The date when the organization was last updated. + */ + readonly isRecurring: boolean, + /** + * Metadata that can be read from the Frontend API and [Backend API](https://clerk.com/docs/reference/backend-api){{ target: '_blank' }} and can be set only from the Backend API. + */ + readonly amount: number, + /** + * Metadata that can be read and set only from the [Backend API](https://clerk.com/docs/reference/backend-api){{ target: '_blank' }}. + */ + readonly period: 'month' | 'annual', + /** + * The maximum number of memberships allowed in the organization. + */ + readonly interval: number, + /** + * Whether the organization allows admins to delete users. + */ + readonly hasBaseFee: boolean, + /** + * The number of members in the organization. + */ + readonly currency: string, + /** + * The ID of the user who created the organization. + */ + readonly annualMonthlyAmount: number, + /** + * Whether the organization allows admins to delete users. + */ + readonly publiclyVisible: boolean, + readonly fee: CommerceAmount, + readonly annualFee: CommerceAmount, + readonly annualMonthlyFee: CommerceAmount, + readonly forPayerType: 'org' | 'user', + readonly features: Feature[], + ) {} + + static fromJSON(data: CommercePlanJSON): CommercePlan { + console.log('data', data); + const formatAmountJSON = (fee: CommercePlanJSON['fee']) => { + return { + amount: fee.amount, + amountFormatted: fee.amount_formatted, + currency: fee.currency, + currencySymbol: fee.currency_symbol, + }; + }; + return new CommercePlan( + data.id, + data.product_id, + data.name, + data.slug, + data.description, + data.is_default, + data.is_recurring, + data.amount, + data.period, + data.interval, + data.has_base_fee, + data.currency, + data.annual_monthly_amount, + data.publicly_visible, + formatAmountJSON(data.fee), + formatAmountJSON(data.annual_fee), + formatAmountJSON(data.annual_monthly_fee), + data.for_payer_type, + data.features.map(feature => Feature.fromJSON(feature)), + ); + } +} diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index d18bf76c039..e8f6b6bc68c 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -37,6 +37,8 @@ import { User, } from '.'; import { AccountlessApplication } from './AccountlessApplication'; +import { CommercePlan } from './CommercePlan'; +import { Feature } from './Feature'; import type { PaginatedResponseJSON } from './JSON'; import { ObjectType } from './JSON'; import { WaitlistEntry } from './WaitlistEntry'; @@ -179,6 +181,10 @@ function jsonToObject(item: any): any { return User.fromJSON(item); case ObjectType.WaitlistEntry: return WaitlistEntry.fromJSON(item); + case ObjectType.CommercePlan: + return CommercePlan.fromJSON(item); + case ObjectType.Feature: + return Feature.fromJSON(item); default: return item; } diff --git a/packages/backend/src/api/resources/Feature.ts b/packages/backend/src/api/resources/Feature.ts new file mode 100644 index 00000000000..f1fa1d48774 --- /dev/null +++ b/packages/backend/src/api/resources/Feature.ts @@ -0,0 +1,15 @@ +import type { FeatureJSON } from './JSON'; + +export class Feature { + constructor( + readonly id: string, + readonly name: string, + readonly description: string, + readonly slug: string, + readonly avatarUrl: string, + ) {} + + static fromJSON(data: FeatureJSON): Feature { + return new Feature(data.id, data.name, data.description, data.slug, data.avatar_url); + } +} diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index a1e216f328d..18b5cab57e9 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -69,6 +69,8 @@ export const ObjectType = { CommercePaymentAttempt: 'commerce_payment_attempt', CommerceSubscription: 'commerce_subscription', CommerceSubscriptionItem: 'commerce_subscription_item', + CommercePlan: 'commerce_plan', + Feature: 'feature', } as const; export type ObjectType = (typeof ObjectType)[keyof typeof ObjectType]; @@ -792,52 +794,37 @@ export interface CommercePayerJSON extends ClerkResourceJSON { updated_at: number; } -export interface CommercePayeeJSON { +interface CommercePayeeJSON { id: string; gateway_type: string; gateway_external_id: string; gateway_status: 'active' | 'pending' | 'restricted' | 'disconnected'; } -export interface CommerceAmountJSON { +interface CommerceAmountJSON { amount: number; amount_formatted: string; currency: string; currency_symbol: string; } -export interface CommerceTotalsJSON { +interface CommerceTotalsJSON { subtotal: CommerceAmountJSON; tax_total: CommerceAmountJSON; grand_total: CommerceAmountJSON; } -export interface CommercePaymentSourceJSON { - id: string; - gateway: string; - gateway_external_id: string; - gateway_external_account_id?: string; - payment_method: string; - status: 'active' | 'disconnected'; - card_type?: string; - last4?: string; -} - -export interface CommercePaymentFailedReasonJSON { - code: string; - decline_code: string; -} - -export interface CommerceSubscriptionCreditJSON { - amount: CommerceAmountJSON; - cycle_days_remaining: number; - cycle_days_total: number; - cycle_remaining_percent: number; +export interface FeatureJSON extends ClerkResourceJSON { + object: typeof ObjectType.Feature; + name: string; + description: string; + slug: string; + avatar_url: string; } -export interface CommercePlanJSON { +export interface CommercePlanJSON extends ClerkResourceJSON { + object: typeof ObjectType.CommercePlan; id: string; - instance_id: string; product_id: string; name: string; slug: string; @@ -846,17 +833,28 @@ export interface CommercePlanJSON { is_recurring: boolean; amount: number; period: 'month' | 'annual'; + // What is this ? interval: number; has_base_fee: boolean; currency: string; annual_monthly_amount: number; publicly_visible: boolean; + fee: CommerceAmountJSON; + annual_fee: CommerceAmountJSON; + annual_monthly_fee: CommerceAmountJSON; + for_payer_type: 'org' | 'user'; + features: FeatureJSON[]; } export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON { object: typeof ObjectType.CommerceSubscriptionItem; status: 'abandoned' | 'active' | 'canceled' | 'ended' | 'expired' | 'incomplete' | 'past_due' | 'upcoming'; - credit: CommerceSubscriptionCreditJSON; + credit: { + amount: CommerceAmountJSON; + cycle_days_remaining: number; + cycle_days_total: number; + cycle_remaining_percent: number; + }; proration_date: string; plan_period: 'month' | 'annual'; period_start: number; @@ -867,7 +865,23 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON { next_payment_amount: number; next_payment_date: number; amount: CommerceAmountJSON; - plan: CommercePlanJSON; + plan: { + id: string; + instance_id: string; + product_id: string; + name: string; + slug: string; + description?: string; + is_default: boolean; + is_recurring: boolean; + amount: number; + period: 'month' | 'annual'; + interval: number; + has_base_fee: boolean; + currency: string; + annual_monthly_amount: number; + publicly_visible: boolean; + }; plan_id: string; } @@ -882,13 +896,25 @@ export interface CommercePaymentAttemptJSON extends ClerkResourceJSON { updated_at: number; paid_at?: number; failed_at?: number; - failed_reason?: CommercePaymentFailedReasonJSON; + failed_reason?: { + code: string; + decline_code: string; + }; billing_date: number; charge_type: 'checkout' | 'recurring'; payee: CommercePayeeJSON; payer: CommercePayerJSON; totals: CommerceTotalsJSON; - payment_source: CommercePaymentSourceJSON; + payment_source: { + id: string; + gateway: string; + gateway_external_id: string; + gateway_external_account_id?: string; + payment_method: string; + status: 'active' | 'disconnected'; + card_type?: string; + last4?: string; + }; subscription_items: CommerceSubscriptionItemJSON[]; } diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 6f4e3aee673..6f83004df27 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -101,12 +101,6 @@ export type { TestingTokenJSON, WebhooksSvixJSON, CommercePayerJSON, - CommercePayeeJSON, - CommerceAmountJSON, - CommerceTotalsJSON, - CommercePaymentSourceJSON, - CommercePaymentFailedReasonJSON, - CommerceSubscriptionCreditJSON, CommercePlanJSON, CommerceSubscriptionItemJSON, CommercePaymentAttemptJSON, From ee3edef3b01ad91ae5df19cb96b0ee72e08662d5 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 31 Jul 2025 16:18:59 +0300 Subject: [PATCH 2/9] add plan to export --- packages/backend/src/api/resources/index.ts | 1 + packages/backend/src/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index 0e7cb401791..a05c133315a 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -57,6 +57,7 @@ export * from './User'; export * from './Verification'; export * from './WaitlistEntry'; export * from './Web3Wallet'; +export * from './CommercePlan'; export type { EmailWebhookEvent, diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 6f83004df27..3e3341e2642 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -144,6 +144,7 @@ export type { Token, User, TestingToken, + CommercePlan, } from './api/resources'; /** From 289eeda413de243358f06d1ab584dc5b78501805 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 31 Jul 2025 19:57:01 +0300 Subject: [PATCH 3/9] remove logs --- packages/backend/src/api/resources/CommercePlan.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/api/resources/CommercePlan.ts b/packages/backend/src/api/resources/CommercePlan.ts index fd59665ebc3..3db5d6d331a 100644 --- a/packages/backend/src/api/resources/CommercePlan.ts +++ b/packages/backend/src/api/resources/CommercePlan.ts @@ -78,7 +78,6 @@ export class CommercePlan { ) {} static fromJSON(data: CommercePlanJSON): CommercePlan { - console.log('data', data); const formatAmountJSON = (fee: CommercePlanJSON['fee']) => { return { amount: fee.amount, From fbaffb22da62b11f500e2fb81b5081e00ffd45bd Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 4 Aug 2025 17:01:52 +0300 Subject: [PATCH 4/9] remove fields and add comments --- .../backend/src/api/resources/CommercePlan.ts | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/packages/backend/src/api/resources/CommercePlan.ts b/packages/backend/src/api/resources/CommercePlan.ts index 3db5d6d331a..12518d47ba5 100644 --- a/packages/backend/src/api/resources/CommercePlan.ts +++ b/packages/backend/src/api/resources/CommercePlan.ts @@ -1,11 +1,7 @@ import { Feature } from './Feature'; import type { CommercePlanJSON } from './JSON'; -/** - * The Backend `Organization` object is similar to the [`Organization`](https://clerk.com/docs/references/javascript/organization) object as it holds information about an organization, as well as methods for managing it. However, the Backend `Organization` object is different in that it is used in the [Backend API](https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/ListOrganizations){{ target: '_blank' }} and is not directly accessible from the Frontend API. - */ - -type CommerceAmount = { +type CommerceFee = { amount: number; amountFormatted: string; currency: string; @@ -15,65 +11,60 @@ type CommerceAmount = { export class CommercePlan { constructor( /** - * The unique identifier for the organization. + * The unique identifier for the plan. */ readonly id: string, /** - * The name of the organization. + * The id of the product the plan belongs to. */ readonly productId: string, /** - * The URL-friendly identifier of the user's active organization. If supplied, it must be unique for the instance. + * The name of the plan. */ readonly name: string, /** - * Holds the organization's logo. Compatible with Clerk's [Image Optimization](https://clerk.com/docs/guides/image-optimization). + * The URL-friendly identifier of the plan. */ readonly slug: string, /** - * Whether the organization has an image. + * The description of the plan. */ readonly description: string | undefined, /** - * The date when the organization was first created. + * Whether the plan is the default plan. */ readonly isDefault: boolean, /** - * The date when the organization was last updated. + * Whether the plan is recurring. */ readonly isRecurring: boolean, /** - * Metadata that can be read from the Frontend API and [Backend API](https://clerk.com/docs/reference/backend-api){{ target: '_blank' }} and can be set only from the Backend API. + * Whether the plan has a base fee. */ - readonly amount: number, + readonly hasBaseFee: boolean, /** - * Metadata that can be read and set only from the [Backend API](https://clerk.com/docs/reference/backend-api){{ target: '_blank' }}. + * Whether the plan is displayed in the `` component. */ - readonly period: 'month' | 'annual', + readonly publiclyVisible: boolean, /** - * The maximum number of memberships allowed in the organization. + * The monthly fee of the plan. */ - readonly interval: number, + readonly fee: CommerceFee, /** - * Whether the organization allows admins to delete users. + * The annual fee of the plan. */ - readonly hasBaseFee: boolean, + readonly annualFee: CommerceFee, /** - * The number of members in the organization. + * The annual fee of the plan on a monthly basis. */ - readonly currency: string, + readonly annualMonthlyFee: CommerceFee, /** - * The ID of the user who created the organization. + * The type of payer for the plan. */ - readonly annualMonthlyAmount: number, + readonly forPayerType: 'org' | 'user', /** - * Whether the organization allows admins to delete users. + * The features the plan offers. */ - readonly publiclyVisible: boolean, - readonly fee: CommerceAmount, - readonly annualFee: CommerceAmount, - readonly annualMonthlyFee: CommerceAmount, - readonly forPayerType: 'org' | 'user', readonly features: Feature[], ) {} @@ -94,12 +85,7 @@ export class CommercePlan { data.description, data.is_default, data.is_recurring, - data.amount, - data.period, - data.interval, data.has_base_fee, - data.currency, - data.annual_monthly_amount, data.publicly_visible, formatAmountJSON(data.fee), formatAmountJSON(data.annual_fee), From 4b43d1489f5efeea873354432bfe527c8146303b Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 4 Aug 2025 17:15:54 +0300 Subject: [PATCH 5/9] add query by payer type --- packages/backend/src/api/endpoints/BillingApi.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/api/endpoints/BillingApi.ts b/packages/backend/src/api/endpoints/BillingApi.ts index a82fac1920b..b39c9e476eb 100644 --- a/packages/backend/src/api/endpoints/BillingApi.ts +++ b/packages/backend/src/api/endpoints/BillingApi.ts @@ -7,7 +7,9 @@ import { AbstractAPI } from './AbstractApi'; const basePath = '/commerce'; -type GetOrganizationListParams = ClerkPaginationRequest; +type GetOrganizationListParams = ClerkPaginationRequest<{ + payerType: 'org' | 'user'; +}>; export class BillingAPI extends AbstractAPI { public async getPlanList(params?: GetOrganizationListParams) { From ae89b789796eb532b006ec0428a6848a58c01776 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 4 Aug 2025 17:29:54 +0300 Subject: [PATCH 6/9] update json types --- packages/backend/src/api/resources/JSON.ts | 24 ++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 18b5cab57e9..0239c406041 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -801,7 +801,7 @@ interface CommercePayeeJSON { gateway_status: 'active' | 'pending' | 'restricted' | 'disconnected'; } -interface CommerceAmountJSON { +interface CommerceFeeJSON { amount: number; amount_formatted: string; currency: string; @@ -809,9 +809,9 @@ interface CommerceAmountJSON { } interface CommerceTotalsJSON { - subtotal: CommerceAmountJSON; - tax_total: CommerceAmountJSON; - grand_total: CommerceAmountJSON; + subtotal: CommerceFeeJSON; + tax_total: CommerceFeeJSON; + grand_total: CommerceFeeJSON; } export interface FeatureJSON extends ClerkResourceJSON { @@ -831,17 +831,11 @@ export interface CommercePlanJSON extends ClerkResourceJSON { description?: string; is_default: boolean; is_recurring: boolean; - amount: number; - period: 'month' | 'annual'; - // What is this ? - interval: number; has_base_fee: boolean; - currency: string; - annual_monthly_amount: number; publicly_visible: boolean; - fee: CommerceAmountJSON; - annual_fee: CommerceAmountJSON; - annual_monthly_fee: CommerceAmountJSON; + fee: CommerceFeeJSON; + annual_fee: CommerceFeeJSON; + annual_monthly_fee: CommerceFeeJSON; for_payer_type: 'org' | 'user'; features: FeatureJSON[]; } @@ -850,7 +844,7 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON { object: typeof ObjectType.CommerceSubscriptionItem; status: 'abandoned' | 'active' | 'canceled' | 'ended' | 'expired' | 'incomplete' | 'past_due' | 'upcoming'; credit: { - amount: CommerceAmountJSON; + amount: CommerceFeeJSON; cycle_days_remaining: number; cycle_days_total: number; cycle_remaining_percent: number; @@ -864,7 +858,7 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON { lifetime_paid: number; next_payment_amount: number; next_payment_date: number; - amount: CommerceAmountJSON; + amount: CommerceFeeJSON; plan: { id: string; instance_id: string; From 7ef81fab624a849a6d7200307e4f2dbd3befea0d Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 7 Aug 2025 13:09:45 +0300 Subject: [PATCH 7/9] add notices --- packages/backend/src/api/endpoints/BillingApi.ts | 4 ++++ packages/backend/src/api/factory.ts | 4 ++++ packages/backend/src/api/resources/CommercePlan.ts | 4 ++++ packages/backend/src/api/resources/JSON.ts | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/packages/backend/src/api/endpoints/BillingApi.ts b/packages/backend/src/api/endpoints/BillingApi.ts index b39c9e476eb..64f7ecd09a4 100644 --- a/packages/backend/src/api/endpoints/BillingApi.ts +++ b/packages/backend/src/api/endpoints/BillingApi.ts @@ -12,6 +12,10 @@ type GetOrganizationListParams = ClerkPaginationRequest<{ }>; export class BillingAPI extends AbstractAPI { + /** + * @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 getPlanList(params?: GetOrganizationListParams) { return this.request>({ method: 'GET', diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index c093e615627..4cd83dd1697 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -53,6 +53,10 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { ), betaFeatures: new BetaFeaturesAPI(request), blocklistIdentifiers: new BlocklistIdentifierAPI(request), + /** + * @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. + */ billing: new BillingAPI(request), clients: new ClientAPI(request), domains: new DomainAPI(request), diff --git a/packages/backend/src/api/resources/CommercePlan.ts b/packages/backend/src/api/resources/CommercePlan.ts index 12518d47ba5..931729e74e0 100644 --- a/packages/backend/src/api/resources/CommercePlan.ts +++ b/packages/backend/src/api/resources/CommercePlan.ts @@ -8,6 +8,10 @@ type CommerceFee = { currencySymbol: string; }; +/** + * @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. + */ export class CommercePlan { constructor( /** diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 0239c406041..d539f7bb7e9 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -822,6 +822,10 @@ export interface FeatureJSON extends ClerkResourceJSON { avatar_url: string; } +/** + * @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. + */ export interface CommercePlanJSON extends ClerkResourceJSON { object: typeof ObjectType.CommercePlan; id: string; From 8ae406cc41497ac5ad7e1f949164eb46363306e3 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 7 Aug 2025 13:11:35 +0300 Subject: [PATCH 8/9] changelog --- .changeset/early-bats-shout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/early-bats-shout.md b/.changeset/early-bats-shout.md index 31ac584f0cc..0b5f0b361eb 100644 --- a/.changeset/early-bats-shout.md +++ b/.changeset/early-bats-shout.md @@ -2,4 +2,4 @@ '@clerk/backend': minor --- -WIP +Add billing API for fetching available plans. From e04a74bfc08f74c0c91c0d80b53635cc0c286669 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 7 Aug 2025 20:49:40 +0300 Subject: [PATCH 9/9] update typedocs --- .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap index 91dc98edd0f..be155abe172 100644 --- a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap +++ b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap @@ -221,6 +221,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = ` "backend/auth-object.mdx", "backend/authenticate-request-options.mdx", "backend/client.mdx", + "backend/commerce-plan-json.mdx", + "backend/commerce-plan.mdx", "backend/email-address.mdx", "backend/external-account.mdx", "backend/get-auth-fn.mdx",