Skip to content

Commit 613cb97

Browse files
authored
feat(backend, clerk-js): Update the supported API version to 2025-11-10 (#7095)
1 parent 892d5b4 commit 613cb97

40 files changed

+389
-279
lines changed

.changeset/hot-jars-smell.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@clerk/shared': minor
3+
'@clerk/agent-toolkit': minor
4+
'@clerk/astro': minor
5+
'@clerk/backend': minor
6+
'@clerk/chrome-extension': minor
7+
'@clerk/clerk-expo': minor
8+
'@clerk/clerk-js': minor
9+
'@clerk/fastify': minor
10+
'@clerk/nextjs': minor
11+
'@clerk/nuxt': minor
12+
'@clerk/clerk-react': minor
13+
'@clerk/react-router': minor
14+
'@clerk/tanstack-react-start': minor
15+
'@clerk/types': minor
16+
'@clerk/vue': minor
17+
---
18+
19+
Update the supported API version to `2025-11-10`.

integration/tests/pricing-table.test.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Locator } from '@playwright/test';
12
import { expect, test } from '@playwright/test';
23

34
import { appConfigs } from '../presets';
@@ -278,14 +279,22 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
278279
// Verify checkout shows trial details
279280
await expect(u.po.checkout.root.getByText('Checkout')).toBeVisible();
280281
await expect(u.po.checkout.root.getByText('Free trial')).toBeVisible();
281-
await expect(u.po.checkout.root.getByText('Total Due after')).toBeVisible();
282+
const title = /^Total Due after trial ends in \d+ days$/i;
283+
await expect(matchLineItem(u.po.checkout.root, title, '$999.00')).toBeVisible();
282284

283285
await u.po.checkout.fillTestCard();
284286
await u.po.checkout.clickPayOrSubscribe();
285287

286288
await expect(u.po.checkout.root.getByText(/Trial.*successfully.*started/i)).toBeVisible({
287289
timeout: 15_000,
288290
});
291+
292+
const footer = u.po.checkout.root.locator('.cl-drawerFooter');
293+
await expect(matchLineItem(footer, 'Total paid', '$0.00')).toBeVisible();
294+
await expect(matchLineItem(footer, 'Trial ends on')).toBeVisible();
295+
await expect(matchLineItem(footer, 'Payment method', 'Visa ⋯ 4242')).toBeVisible();
296+
expect(await countLineItems(footer)).toBe(3);
297+
289298
await u.po.checkout.confirmAndContinue();
290299

291300
await u.po.page.goToRelative('/pricing-table');
@@ -344,12 +353,18 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
344353
// Verify checkout shows trial details
345354
await expect(u.po.checkout.root.getByText('Checkout')).toBeVisible();
346355
await expect(u.po.checkout.root.getByText('Free trial')).toBeHidden();
347-
await expect(u.po.checkout.root.getByText('Total Due after')).toBeHidden();
348-
await expect(u.po.checkout.root.getByText('Total Due Today')).toBeVisible();
356+
357+
await expect(matchLineItem(u.po.checkout.root, 'Total Due after')).toBeHidden();
358+
await expect(matchLineItem(u.po.checkout.root, 'Subtotal', '$999.00')).toBeVisible();
359+
await expect(matchLineItem(u.po.checkout.root, 'Total Due Today', '$999.00')).toBeVisible();
360+
expect(await countLineItems(u.po.checkout.root)).toBe(3);
349361

350362
await u.po.checkout.root.getByRole('button', { name: /^pay\s\$/i }).waitFor({ state: 'visible' });
351363
await u.po.checkout.clickPayOrSubscribe();
352-
await expect(u.po.page.getByText('Payment was successful!')).toBeVisible();
364+
await expect(u.po.checkout.root.getByText('Payment was successful!')).toBeVisible();
365+
await expect(matchLineItem(footer, 'Total paid', '$999.00')).toBeVisible();
366+
await expect(matchLineItem(footer, 'Payment method', 'Visa ⋯ 4242')).toBeVisible();
367+
expect(await countLineItems(footer)).toBe(2);
353368
await u.po.checkout.confirmAndContinue();
354369

355370
await u.po.page.goToRelative('/user');
@@ -877,3 +892,34 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
877892
});
878893
});
879894
});
895+
896+
/**
897+
* Helper to match a line item by its title and optionally its description.
898+
* Line items are rendered as Clerk LineItems components with element descriptors:
899+
* - .cl-lineItemsTitle contains the title
900+
* - .cl-lineItemsDescription contains the description (immediately following the title)
901+
*/
902+
function matchLineItem(root: Locator, title: string | RegExp, description?: string | RegExp): Locator {
903+
// Find the title element using the Clerk-generated class
904+
const titleElement = root.locator('.cl-lineItemsTitle').filter({ hasText: title });
905+
906+
// If no description is provided, return the title element
907+
if (description === undefined) {
908+
return titleElement;
909+
}
910+
911+
// Get the next sibling description element using the Clerk-generated class
912+
const descriptionElement = titleElement
913+
.locator('xpath=following-sibling::*[1][contains(@class, "cl-lineItemsDescription")]')
914+
.filter({ hasText: description });
915+
916+
return descriptionElement;
917+
}
918+
919+
/**
920+
* Helper to count the number of line items within a given root element.
921+
* Line items are rendered as Clerk LineItems components where each .cl-lineItemsTitle represents a line item.
922+
*/
923+
async function countLineItems(root: Locator): Promise<number> {
924+
return await root.locator('.cl-lineItemsTitle').count();
925+
}

packages/backend/src/api/resources/CommercePlan.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { BillingMoneyAmount } from '@clerk/types';
1+
import type { BillingMoneyAmount, BillingMoneyAmountJSON } from '@clerk/shared/types';
22

33
import { Feature } from './Feature';
44
import type { BillingPlanJSON } from './JSON';
@@ -14,10 +14,6 @@ export class BillingPlan {
1414
* The unique identifier for the plan.
1515
*/
1616
readonly id: string,
17-
/**
18-
* The ID of the product the plan belongs to.
19-
*/
20-
readonly productId: string,
2117
/**
2218
* The name of the plan.
2319
*/
@@ -29,7 +25,7 @@ export class BillingPlan {
2925
/**
3026
* The description of the plan.
3127
*/
32-
readonly description: string | undefined,
28+
readonly description: string | null,
3329
/**
3430
* Whether the plan is the default plan.
3531
*/
@@ -53,11 +49,11 @@ export class BillingPlan {
5349
/**
5450
* The annual fee of the plan.
5551
*/
56-
readonly annualFee: BillingMoneyAmount,
52+
readonly annualFee: BillingMoneyAmount | null,
5753
/**
5854
* The annual fee of the plan on a monthly basis.
5955
*/
60-
readonly annualMonthlyFee: BillingMoneyAmount,
56+
readonly annualMonthlyFee: BillingMoneyAmount | null,
6157
/**
6258
* The type of payer for the plan.
6359
*/
@@ -69,20 +65,25 @@ export class BillingPlan {
6965
) {}
7066

7167
static fromJSON(data: BillingPlanJSON): BillingPlan {
72-
const formatAmountJSON = (fee: BillingPlanJSON['fee']) => {
73-
return {
74-
amount: fee.amount,
75-
amountFormatted: fee.amount_formatted,
76-
currency: fee.currency,
77-
currencySymbol: fee.currency_symbol,
78-
};
68+
const formatAmountJSON = <T extends BillingMoneyAmountJSON | null>(
69+
fee: T,
70+
): T extends null ? null : BillingMoneyAmount => {
71+
return (
72+
fee
73+
? {
74+
amount: fee.amount,
75+
amountFormatted: fee.amount_formatted,
76+
currency: fee.currency,
77+
currencySymbol: fee.currency_symbol,
78+
}
79+
: null
80+
) as T extends null ? null : BillingMoneyAmount;
7981
};
8082
return new BillingPlan(
8183
data.id,
82-
data.product_id,
8384
data.name,
8485
data.slug,
85-
data.description,
86+
data.description ?? null,
8687
data.is_default,
8788
data.is_recurring,
8889
data.has_base_fee,
@@ -91,7 +92,7 @@ export class BillingPlan {
9192
formatAmountJSON(data.annual_fee),
9293
formatAmountJSON(data.annual_monthly_fee),
9394
data.for_payer_type,
94-
data.features.map(feature => Feature.fromJSON(feature)),
95+
(data.features ?? []).map(feature => Feature.fromJSON(feature)),
9596
);
9697
}
9798
}

packages/backend/src/api/resources/CommerceSubscription.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class BillingSubscription {
7373
data.updated_at,
7474
data.active_at ?? null,
7575
data.past_due_at ?? null,
76-
data.subscription_items.map(item => BillingSubscriptionItem.fromJSON(item)),
76+
(data.subscription_items ?? []).map(item => BillingSubscriptionItem.fromJSON(item)),
7777
nextPayment,
7878
data.eligible_for_free_trial ?? false,
7979
);

packages/backend/src/api/resources/CommerceSubscriptionItem.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,23 @@ export class BillingSubscriptionItem {
2929
/**
3030
* The next payment information.
3131
*/
32-
readonly nextPayment: {
33-
/**
34-
* The amount of the next payment.
35-
*/
36-
amount: number;
37-
/**
38-
* Unix timestamp (milliseconds) of when the next payment is scheduled.
39-
*/
40-
date: number;
41-
} | null,
32+
readonly nextPayment:
33+
| {
34+
/**
35+
* The amount of the next payment.
36+
*/
37+
amount: number;
38+
/**
39+
* Unix timestamp (milliseconds) of when the next payment is scheduled.
40+
*/
41+
date: number;
42+
}
43+
| null
44+
| undefined,
4245
/**
4346
* The current amount for the subscription item.
4447
*/
45-
readonly amount: BillingMoneyAmount | null | undefined,
48+
readonly amount: BillingMoneyAmount | undefined,
4649
/**
4750
* The plan associated with this subscription item.
4851
*/
@@ -78,15 +81,15 @@ export class BillingSubscriptionItem {
7881
/**
7982
* The payer ID.
8083
*/
81-
readonly payerId: string,
84+
readonly payerId: string | undefined,
8285
/**
8386
* Whether this subscription item is currently in a free trial period.
8487
*/
8588
readonly isFreeTrial?: boolean,
8689
/**
8790
* The lifetime amount paid for this subscription item.
8891
*/
89-
readonly lifetimePaid?: BillingMoneyAmount | null,
92+
readonly lifetimePaid?: BillingMoneyAmount,
9093
) {}
9194

9295
static fromJSON(data: BillingSubscriptionItemJSON): BillingSubscriptionItem {
@@ -111,7 +114,7 @@ export class BillingSubscriptionItem {
111114
data.plan_period,
112115
data.period_start,
113116
data.next_payment,
114-
formatAmountJSON(data.amount),
117+
formatAmountJSON(data.amount) ?? undefined,
115118
data.plan ? BillingPlan.fromJSON(data.plan) : null,
116119
data.plan_id ?? null,
117120
data.created_at,
@@ -122,7 +125,7 @@ export class BillingSubscriptionItem {
122125
data.ended_at,
123126
data.payer_id,
124127
data.is_free_trial,
125-
formatAmountJSON(data.lifetime_paid),
128+
formatAmountJSON(data.lifetime_paid) ?? undefined,
126129
);
127130
}
128131
}

packages/backend/src/api/resources/Feature.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ export class Feature {
1818
/**
1919
* The description of the feature.
2020
*/
21-
readonly description: string,
21+
readonly description: string | null,
2222
/**
2323
* The URL-friendly identifier of the feature.
2424
*/
2525
readonly slug: string,
2626
/**
2727
* The URL of the feature's avatar image.
2828
*/
29-
readonly avatarUrl: string,
29+
readonly avatarUrl: string | null,
3030
) {}
3131

3232
static fromJSON(data: FeatureJSON): Feature {
33-
return new Feature(data.id, data.name, data.description, data.slug, data.avatar_url);
33+
return new Feature(data.id, data.name, data.description ?? null, data.slug, data.avatar_url ?? null);
3434
}
3535
}

packages/backend/src/api/resources/JSON.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,9 @@ interface BillingTotalsJSON {
834834
export interface FeatureJSON extends ClerkResourceJSON {
835835
object: typeof ObjectType.Feature;
836836
name: string;
837-
description: string;
837+
description?: string | null;
838838
slug: string;
839-
avatar_url: string;
839+
avatar_url?: string | null;
840840
}
841841

842842
/**
@@ -845,19 +845,18 @@ export interface FeatureJSON extends ClerkResourceJSON {
845845
export interface BillingPlanJSON extends ClerkResourceJSON {
846846
object: typeof ObjectType.BillingPlan;
847847
id: string;
848-
product_id: string;
849848
name: string;
850849
slug: string;
851-
description?: string;
850+
description: string | null;
852851
is_default: boolean;
853852
is_recurring: boolean;
854853
has_base_fee: boolean;
855854
publicly_visible: boolean;
856855
fee: BillingMoneyAmountJSON;
857-
annual_fee: BillingMoneyAmountJSON;
858-
annual_monthly_fee: BillingMoneyAmountJSON;
856+
annual_fee: BillingMoneyAmountJSON | null;
857+
annual_monthly_fee: BillingMoneyAmountJSON | null;
859858
for_payer_type: 'org' | 'user';
860-
features: FeatureJSON[];
859+
features?: FeatureJSON[];
861860
}
862861

863862
type BillingSubscriptionItemStatus =
@@ -877,7 +876,7 @@ export interface BillingSubscriptionItemJSON extends ClerkResourceJSON {
877876
object: typeof ObjectType.BillingSubscriptionItem;
878877
status: BillingSubscriptionItemStatus;
879878
plan_period: 'month' | 'annual';
880-
payer_id: string;
879+
payer_id?: string;
881880
period_start: number;
882881
period_end: number | null;
883882
is_free_trial?: boolean;
@@ -886,12 +885,12 @@ export interface BillingSubscriptionItemJSON extends ClerkResourceJSON {
886885
updated_at: number;
887886
canceled_at: number | null;
888887
past_due_at: number | null;
889-
lifetime_paid: BillingMoneyAmountJSON;
890-
next_payment: {
888+
lifetime_paid: BillingMoneyAmountJSON | null;
889+
next_payment?: {
891890
amount: number;
892891
date: number;
893892
} | null;
894-
amount: BillingMoneyAmountJSON | null;
893+
amount: BillingMoneyAmountJSON;
895894
plan?: BillingPlanJSON | null;
896895
plan_id?: string | null;
897896
}

packages/backend/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const API_VERSION = 'v1';
33

44
export const USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
55
export const MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;
6-
export const SUPPORTED_BAPI_VERSION = '2025-04-10';
6+
export const SUPPORTED_BAPI_VERSION = '2025-11-10';
77

88
const Attributes = {
99
AuthToken: '__clerkAuthToken',

packages/backend/src/tokens/__tests__/handshake.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ describe('HandshakeService', () => {
427427

428428
// Verify all required parameters are present
429429
expect(url.searchParams.get('redirect_url')).toBeDefined();
430-
expect(url.searchParams.get('__clerk_api_version')).toBe('2025-04-10');
430+
expect(url.searchParams.get('__clerk_api_version')).toBe('2025-11-10');
431431
expect(url.searchParams.get(constants.QueryParameters.SuffixedCookies)).toMatch(/^(true|false)$/);
432432
expect(url.searchParams.get(constants.QueryParameters.HandshakeReason)).toBe('test-reason');
433433
});

packages/clerk-js/bundlewatch.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
{ "path": "./dist/waitlist*.js", "maxSize": "1.5KB" },
2525
{ "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" },
2626
{ "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" },
27-
{ "path": "./dist/checkout*.js", "maxSize": "8.8KB" },
27+
{ "path": "./dist/checkout*.js", "maxSize": "8.82KB" },
2828
{ "path": "./dist/up-billing-page*.js", "maxSize": "3.0KB" },
2929
{ "path": "./dist/op-billing-page*.js", "maxSize": "3.0KB" },
3030
{ "path": "./dist/up-plans-page*.js", "maxSize": "1.0KB" },

0 commit comments

Comments
 (0)