Skip to content

Commit 9a00a1c

Browse files
dstaleyjacekradko
andauthored
feat(*): Seat-based billing support (#8006)
Co-authored-by: Jacek <jacek@clerk.dev>
1 parent 849f198 commit 9a00a1c

File tree

26 files changed

+2053
-299
lines changed

26 files changed

+2053
-299
lines changed

.changeset/cute-ideas-appear.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/localizations': minor
3+
'@clerk/clerk-js': minor
4+
'@clerk/shared': minor
5+
'@clerk/ui': minor
6+
---
7+
8+
Add support for seat-based billing plans in Clerk Billing.

packages/clerk-js/bundlewatch.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": [
33
{ "path": "./dist/clerk.js", "maxSize": "543KB" },
4-
{ "path": "./dist/clerk.browser.js", "maxSize": "67KB" },
4+
{ "path": "./dist/clerk.browser.js", "maxSize": "68KB" },
55
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" },
66
{ "path": "./dist/clerk.no-rhc.js", "maxSize": "309KB" },
77
{ "path": "./dist/clerk.native.js", "maxSize": "68KB" },
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
import {
2+
clerkHandlers,
3+
http,
4+
HttpResponse,
5+
EnvironmentService,
6+
SessionService,
7+
setClerkState,
8+
type MockScenario,
9+
UserService,
10+
} from '@clerk/msw';
11+
12+
export function CheckoutSeats(): MockScenario {
13+
const user = UserService.create();
14+
const session = SessionService.create(user);
15+
16+
setClerkState({
17+
environment: EnvironmentService.MULTI_SESSION,
18+
session,
19+
user,
20+
});
21+
22+
const subscriptionHandler = http.get('https://*.clerk.accounts.dev/v1/me/billing/subscription', () => {
23+
return HttpResponse.json({
24+
response: {
25+
data: {},
26+
},
27+
});
28+
});
29+
30+
const paymentMethodsHandler = http.get('https://*.clerk.accounts.dev/v1/me/billing/payment_methods', () => {
31+
return HttpResponse.json({
32+
response: {
33+
data: {},
34+
},
35+
});
36+
});
37+
38+
const checkoutAccountCreditHandler = http.post('https://*.clerk.accounts.dev/v1/me/billing/checkouts', () => {
39+
return HttpResponse.json({
40+
response: {
41+
object: 'commerce_checkout',
42+
id: 'string',
43+
plan: {
44+
object: 'commerce_plan',
45+
id: 'string',
46+
name: 'Pro',
47+
fee: {
48+
amount: 0,
49+
amount_formatted: '25.00',
50+
currency: 'string',
51+
currency_symbol: '$',
52+
},
53+
annual_monthly_fee: {
54+
amount: 0,
55+
amount_formatted: 'string',
56+
currency: 'string',
57+
currency_symbol: 'string',
58+
},
59+
annual_fee: {
60+
amount: 0,
61+
amount_formatted: 'string',
62+
currency: 'string',
63+
currency_symbol: 'string',
64+
},
65+
description: null,
66+
is_default: true,
67+
is_recurring: true,
68+
publicly_visible: true,
69+
has_base_fee: true,
70+
for_payer_type: 'string',
71+
slug: 'string',
72+
avatar_url: null,
73+
free_trial_enabled: true,
74+
free_trial_days: null,
75+
features: [
76+
{
77+
object: 'feature',
78+
id: 'string',
79+
name: 'string',
80+
description: null,
81+
slug: 'string',
82+
avatar_url: null,
83+
},
84+
],
85+
},
86+
plan_period: 'month',
87+
payer: {
88+
object: 'commerce_payer',
89+
id: 'string',
90+
instance_id: 'string',
91+
user_id: null,
92+
first_name: null,
93+
last_name: null,
94+
email: null,
95+
organization_id: null,
96+
organization_name: null,
97+
image_url: 'https://example.com',
98+
created_at: 1,
99+
updated_at: 1,
100+
},
101+
payment_method: {
102+
object: 'commerce_payment_method',
103+
id: 'string',
104+
payer_id: 'string',
105+
payment_type: 'card',
106+
is_default: true,
107+
gateway: 'string',
108+
gateway_external_id: 'string',
109+
gateway_external_account_id: null,
110+
last4: null,
111+
status: 'active',
112+
wallet_type: null,
113+
card_type: null,
114+
expiry_year: null,
115+
expiry_month: null,
116+
created_at: 1,
117+
updated_at: 1,
118+
is_removable: true,
119+
},
120+
external_gateway_id: 'string',
121+
status: 'needs_confirmation',
122+
totals: {
123+
subtotal: {
124+
amount: 4500,
125+
amount_formatted: '45.00',
126+
currency: 'string',
127+
currency_symbol: '$',
128+
},
129+
tax_total: {
130+
amount: 1,
131+
amount_formatted: 'string',
132+
currency: 'string',
133+
currency_symbol: 'string',
134+
},
135+
grand_total: {
136+
amount: 1,
137+
amount_formatted: 'string',
138+
currency: 'string',
139+
currency_symbol: 'string',
140+
},
141+
total_due_after_free_trial: {
142+
amount: 1,
143+
amount_formatted: 'string',
144+
currency: 'string',
145+
currency_symbol: 'string',
146+
},
147+
total_due_now: {
148+
amount: 4500,
149+
amount_formatted: '45.00',
150+
currency: 'string',
151+
currency_symbol: '$',
152+
},
153+
past_due: null,
154+
credit: {
155+
amount: 1,
156+
amount_formatted: '5.00',
157+
currency: 'string',
158+
currency_symbol: '$',
159+
},
160+
per_unit_totals: [
161+
{
162+
name: 'seats',
163+
block_size: 1,
164+
tiers: [
165+
{
166+
quantity: 10,
167+
fee_per_block: {
168+
amount: 0,
169+
amount_formatted: '0.00',
170+
currency: 'USD',
171+
currency_symbol: '$',
172+
},
173+
total: {
174+
amount: 0,
175+
amount_formatted: '0.00',
176+
currency: 'USD',
177+
currency_symbol: '$',
178+
},
179+
},
180+
{
181+
quantity: 2,
182+
fee_per_block: {
183+
amount: 1000,
184+
amount_formatted: '10.00',
185+
currency: 'USD',
186+
currency_symbol: '$',
187+
},
188+
total: {
189+
amount: 2000,
190+
amount_formatted: '20.00',
191+
currency: 'USD',
192+
currency_symbol: '$',
193+
},
194+
},
195+
],
196+
},
197+
],
198+
},
199+
subscription_item: {
200+
object: 'commerce_subscription_item',
201+
id: 'string',
202+
instance_id: 'string',
203+
status: 'active',
204+
credit: {
205+
amount: {
206+
amount: 1,
207+
amount_formatted: 'string',
208+
currency: 'string',
209+
currency_symbol: 'string',
210+
},
211+
cycle_days_remaining: 1,
212+
cycle_days_total: 1,
213+
cycle_remaining_percent: 1,
214+
},
215+
plan_id: 'string',
216+
price_id: 'string',
217+
plan: {
218+
object: 'commerce_plan',
219+
id: 'string',
220+
name: 'string',
221+
fee: {
222+
amount: 0,
223+
amount_formatted: 'string',
224+
currency: 'string',
225+
currency_symbol: 'string',
226+
},
227+
annual_monthly_fee: {
228+
amount: 0,
229+
amount_formatted: 'string',
230+
currency: 'string',
231+
currency_symbol: 'string',
232+
},
233+
annual_fee: {
234+
amount: 0,
235+
amount_formatted: 'string',
236+
currency: 'string',
237+
currency_symbol: 'string',
238+
},
239+
description: null,
240+
is_default: true,
241+
is_recurring: true,
242+
publicly_visible: true,
243+
has_base_fee: true,
244+
for_payer_type: 'string',
245+
slug: 'string',
246+
avatar_url: null,
247+
free_trial_enabled: true,
248+
free_trial_days: null,
249+
features: [
250+
{
251+
object: 'feature',
252+
id: 'string',
253+
name: 'string',
254+
description: null,
255+
slug: 'string',
256+
avatar_url: null,
257+
},
258+
],
259+
},
260+
plan_period: 'month',
261+
payment_method_id: 'string',
262+
payment_method: {
263+
object: 'commerce_payment_method',
264+
id: 'string',
265+
payer_id: 'string',
266+
payment_type: 'card',
267+
is_default: true,
268+
gateway: 'string',
269+
gateway_external_id: 'string',
270+
gateway_external_account_id: null,
271+
last4: null,
272+
status: 'active',
273+
wallet_type: null,
274+
card_type: null,
275+
expiry_year: null,
276+
expiry_month: null,
277+
created_at: 1,
278+
updated_at: 1,
279+
is_removable: true,
280+
},
281+
lifetime_paid: {
282+
amount: 0,
283+
amount_formatted: 'string',
284+
currency: 'string',
285+
currency_symbol: 'string',
286+
},
287+
amount: {
288+
amount: 0,
289+
amount_formatted: 'string',
290+
currency: 'string',
291+
currency_symbol: 'string',
292+
},
293+
next_payment: {
294+
amount: {
295+
amount: 0,
296+
amount_formatted: 'string',
297+
currency: 'string',
298+
currency_symbol: 'string',
299+
},
300+
date: 1,
301+
},
302+
payer_id: 'string',
303+
payer: {
304+
object: 'commerce_payer',
305+
id: 'string',
306+
instance_id: 'string',
307+
user_id: null,
308+
first_name: null,
309+
last_name: null,
310+
email: null,
311+
organization_id: null,
312+
organization_name: null,
313+
image_url: 'https://example.com',
314+
created_at: 1,
315+
updated_at: 1,
316+
},
317+
is_free_trial: true,
318+
period_start: 1,
319+
period_end: null,
320+
proration_date: 'string',
321+
canceled_at: null,
322+
past_due_at: null,
323+
ended_at: null,
324+
created_at: 1,
325+
updated_at: 1,
326+
},
327+
plan_period_start: 1,
328+
is_immediate_plan_change: true,
329+
free_trial_ends_at: 1,
330+
needs_payment_method: true,
331+
},
332+
});
333+
});
334+
335+
return {
336+
description: 'Checkout with seats',
337+
handlers: [checkoutAccountCreditHandler, subscriptionHandler, paymentMethodsHandler, ...clerkHandlers],
338+
initialState: { session, user },
339+
name: 'checkout-seats',
340+
};
341+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export { UserButtonSignedIn } from './user-button-signed-in';
22
export { CheckoutAccountCredit } from './checkout-account-credit';
3+
export { CheckoutSeats } from './checkout-seats';
4+
export { OrgProfileSeatLimit } from './org-profile-seat-limit';
5+
export { PricingTableSBB } from './pricing-table-sbb';
36
export { AnnualOnlyPlans } from './annual-only-plans';

0 commit comments

Comments
 (0)