Skip to content

Commit ba7f3fd

Browse files
authored
chore(backend): Add getOrganizationBillingSubscription to BillingApi (#6632)
1 parent 2ed539c commit ba7f3fd

File tree

9 files changed

+157
-9
lines changed

9 files changed

+157
-9
lines changed

.changeset/fresh-clubs-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': minor
3+
---
4+
5+
Add `getOrganizationBillingSubscription` to BillingApi.

.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
229229
"backend/commerce-subscription-item-webhook-event-json.mdx",
230230
"backend/commerce-subscription-item.mdx",
231231
"backend/commerce-subscription-webhook-event-json.mdx",
232+
"backend/commerce-subscription.mdx",
232233
"backend/email-address.mdx",
233234
"backend/external-account.mdx",
234235
"backend/get-auth-fn.mdx",

packages/backend/src/api/endpoints/BillingApi.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import type { ClerkPaginationRequest } from '@clerk/types';
22

33
import { joinPaths } from '../../util/path';
44
import type { CommercePlan } from '../resources/CommercePlan';
5+
import type { CommerceSubscription } from '../resources/CommerceSubscription';
56
import type { CommerceSubscriptionItem } from '../resources/CommerceSubscriptionItem';
67
import type { PaginatedResourceResponse } from '../resources/Deserializer';
78
import { AbstractAPI } from './AbstractApi';
89

910
const basePath = '/commerce';
11+
const organizationBasePath = '/organizations';
1012

1113
type GetOrganizationListParams = ClerkPaginationRequest<{
1214
payerType: 'org' | 'user';
@@ -45,4 +47,16 @@ export class BillingAPI extends AbstractAPI {
4547
queryParams: params,
4648
});
4749
}
50+
51+
/**
52+
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
53+
* It is advised to pin the SDK version to avoid breaking changes.
54+
*/
55+
public async getOrganizationBillingSubscription(organizationId: string) {
56+
this.requireId(organizationId);
57+
return this.request<CommerceSubscription>({
58+
method: 'GET',
59+
path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'),
60+
});
61+
}
4862
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { type CommerceMoneyAmount } from './CommercePlan';
2+
import { CommerceSubscriptionItem } from './CommerceSubscriptionItem';
3+
import type { CommerceSubscriptionJSON } from './JSON';
4+
5+
/**
6+
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
7+
* It is advised to pin the SDK version to avoid breaking changes.
8+
*/
9+
export class CommerceSubscription {
10+
constructor(
11+
/**
12+
* The unique identifier for the commerce subscription.
13+
*/
14+
readonly id: string,
15+
/**
16+
* The current status of the subscription.
17+
*/
18+
readonly status: CommerceSubscriptionJSON['status'],
19+
/**
20+
* The ID of the payer for this subscription.
21+
*/
22+
readonly payerId: string,
23+
/**
24+
* Unix timestamp (milliseconds) of creation.
25+
*/
26+
readonly createdAt: number,
27+
/**
28+
* Unix timestamp (milliseconds) of last update.
29+
*/
30+
readonly updatedAt: number,
31+
/**
32+
* Unix timestamp (milliseconds) when the subscription became active.
33+
*/
34+
readonly activeAt: number | null,
35+
/**
36+
* Unix timestamp (milliseconds) when the subscription became past due.
37+
*/
38+
readonly pastDueAt: number | null,
39+
/**
40+
* Array of subscription items in this subscription.
41+
*/
42+
readonly subscriptionItems: CommerceSubscriptionItem[],
43+
/**
44+
* Information about the next scheduled payment.
45+
*/
46+
readonly nextPayment: { date: number; amount: CommerceMoneyAmount } | null,
47+
/**
48+
* Whether the payer is eligible for a free trial.
49+
*/
50+
readonly eligibleForFreeTrial: boolean,
51+
) {}
52+
53+
static fromJSON(data: CommerceSubscriptionJSON): CommerceSubscription {
54+
const nextPayment = data.next_payment
55+
? {
56+
date: data.next_payment.date,
57+
amount: {
58+
amount: data.next_payment.amount.amount,
59+
amountFormatted: data.next_payment.amount.amount_formatted,
60+
currency: data.next_payment.amount.currency,
61+
currencySymbol: data.next_payment.amount.currency_symbol,
62+
},
63+
}
64+
: null;
65+
66+
return new CommerceSubscription(
67+
data.id,
68+
data.status,
69+
data.payer_id,
70+
data.created_at,
71+
data.updated_at,
72+
data.active_at ?? null,
73+
data.past_due_at ?? null,
74+
data.subscription_items.map(item => CommerceSubscriptionItem.fromJSON(item)),
75+
nextPayment,
76+
data.eligible_for_free_trial ?? false,
77+
);
78+
}
79+
}

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,45 @@ export class CommerceSubscriptionItem {
4444
* The plan ID.
4545
*/
4646
readonly planId: string,
47+
/**
48+
* The date and time the subscription item was created.
49+
*/
50+
readonly createdAt: number,
51+
/**
52+
* The date and time the subscription item was last updated.
53+
*/
54+
readonly updatedAt: number,
4755
/**
4856
* The end of the current period.
4957
*/
50-
readonly periodEnd?: number,
58+
readonly periodEnd: number | null,
5159
/**
5260
* When the subscription item was canceled.
5361
*/
54-
readonly canceledAt?: number,
62+
readonly canceledAt: number | null,
5563
/**
5664
* When the subscription item became past due.
5765
*/
58-
readonly pastDueAt?: number,
66+
readonly pastDueAt: number | null,
67+
/**
68+
* When the subscription item ended.
69+
*/
70+
readonly endedAt: number | null,
71+
/**
72+
* The payer ID.
73+
*/
74+
readonly payerId: string,
75+
/**
76+
* Whether this subscription item is currently in a free trial period.
77+
*/
78+
readonly isFreeTrial?: boolean,
5979
/**
6080
* The lifetime amount paid for this subscription item.
6181
*/
6282
readonly lifetimePaid?: CommerceMoneyAmount | null,
6383
) {}
6484

6585
static fromJSON(data: CommerceSubscriptionItemJSON): CommerceSubscriptionItem {
66-
function formatAmountJSON(
67-
amount: CommerceMoneyAmountJSON | null | undefined,
68-
): CommerceMoneyAmount | null | undefined;
6986
function formatAmountJSON(
7087
amount: CommerceMoneyAmountJSON | null | undefined,
7188
): CommerceMoneyAmount | null | undefined {
@@ -90,9 +107,14 @@ export class CommerceSubscriptionItem {
90107
formatAmountJSON(data.amount),
91108
CommercePlan.fromJSON(data.plan),
92109
data.plan_id,
110+
data.created_at,
111+
data.updated_at,
93112
data.period_end,
94113
data.canceled_at,
95114
data.past_due_at,
115+
data.ended_at,
116+
data.payer_id,
117+
data.is_free_trial,
96118
formatAmountJSON(data.lifetime_paid),
97119
);
98120
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
} from '.';
3939
import { AccountlessApplication } from './AccountlessApplication';
4040
import { CommercePlan } from './CommercePlan';
41+
import { CommerceSubscription } from './CommerceSubscription';
4142
import { CommerceSubscriptionItem } from './CommerceSubscriptionItem';
4243
import { Feature } from './Feature';
4344
import type { PaginatedResponseJSON } from './JSON';
@@ -184,6 +185,8 @@ function jsonToObject(item: any): any {
184185
return WaitlistEntry.fromJSON(item);
185186
case ObjectType.CommercePlan:
186187
return CommercePlan.fromJSON(item);
188+
case ObjectType.CommerceSubscription:
189+
return CommerceSubscription.fromJSON(item);
187190
case ObjectType.CommerceSubscriptionItem:
188191
return CommerceSubscriptionItem.fromJSON(item);
189192
case ObjectType.Feature:

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -861,10 +861,15 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
861861
object: typeof ObjectType.CommerceSubscriptionItem;
862862
status: CommerceSubscriptionItemStatus;
863863
plan_period: 'month' | 'annual';
864+
payer_id: string;
864865
period_start: number;
865-
period_end?: number;
866-
canceled_at?: number;
867-
past_due_at?: number;
866+
period_end: number | null;
867+
is_free_trial?: boolean;
868+
ended_at: number | null;
869+
created_at: number;
870+
updated_at: number;
871+
canceled_at: number | null;
872+
past_due_at: number | null;
868873
lifetime_paid: CommerceMoneyAmountJSON;
869874
next_payment: {
870875
amount: number;
@@ -972,6 +977,22 @@ export interface CommerceSubscriptionWebhookEventJSON extends ClerkResourceJSON
972977
items: CommerceSubscriptionItemWebhookEventJSON[];
973978
}
974979

980+
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
981+
object: typeof ObjectType.CommerceSubscription;
982+
status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
983+
payer_id: string;
984+
created_at: number;
985+
updated_at: number;
986+
active_at: number | null;
987+
past_due_at: number | null;
988+
subscription_items: CommerceSubscriptionItemJSON[];
989+
next_payment?: {
990+
date: number;
991+
amount: CommerceMoneyAmountJSON;
992+
};
993+
eligible_for_free_trial?: boolean;
994+
}
995+
975996
export interface WebhooksSvixJSON {
976997
svix_url: string;
977998
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export * from './Verification';
5858
export * from './WaitlistEntry';
5959
export * from './Web3Wallet';
6060
export * from './CommercePlan';
61+
export * from './CommerceSubscription';
6162
export * from './CommerceSubscriptionItem';
6263

6364
export type {

packages/backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export type {
101101
TestingTokenJSON,
102102
WebhooksSvixJSON,
103103
CommercePlanJSON,
104+
CommerceSubscriptionJSON,
104105
CommerceSubscriptionItemJSON,
105106
} from './api/resources/JSON';
106107

@@ -144,6 +145,7 @@ export type {
144145
User,
145146
TestingToken,
146147
CommercePlan,
148+
CommerceSubscription,
147149
CommerceSubscriptionItem,
148150
} from './api/resources';
149151

0 commit comments

Comments
 (0)