Skip to content

Commit 822e4a1

Browse files
authored
feat(backend): Add cancelSubscriptionItem to BillingApi (#6611)
1 parent 8002941 commit 822e4a1

File tree

10 files changed

+192
-15
lines changed

10 files changed

+192
-15
lines changed

.changeset/eight-sheep-matter.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+
[Billing Beta] Add `cancelSubscriptionItem` to BillingApi.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
222222
"backend/auth-object.mdx",
223223
"backend/authenticate-request-options.mdx",
224224
"backend/client.mdx",
225+
"backend/commerce-payment-attempt-webhook-event-json.mdx",
225226
"backend/commerce-plan-json.mdx",
226227
"backend/commerce-plan.mdx",
228+
"backend/commerce-subscription-item-json.mdx",
229+
"backend/commerce-subscription-item-webhook-event-json.mdx",
230+
"backend/commerce-subscription-item.mdx",
231+
"backend/commerce-subscription-webhook-event-json.mdx",
227232
"backend/email-address.mdx",
228233
"backend/external-account.mdx",
229234
"backend/get-auth-fn.mdx",

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

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

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

@@ -11,6 +12,14 @@ type GetOrganizationListParams = ClerkPaginationRequest<{
1112
payerType: 'org' | 'user';
1213
}>;
1314

15+
type CancelSubscriptionItemParams = {
16+
/**
17+
* If true, the subscription item will be canceled immediately. If false or undefined, the subscription item will be canceled at the end of the current billing period.
18+
* @default undefined
19+
*/
20+
endNow?: boolean;
21+
};
22+
1423
export class BillingAPI extends AbstractAPI {
1524
/**
1625
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
@@ -23,4 +32,17 @@ export class BillingAPI extends AbstractAPI {
2332
queryParams: params,
2433
});
2534
}
35+
36+
/**
37+
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
38+
* It is advised to pin the SDK version to avoid breaking changes.
39+
*/
40+
public async cancelSubscriptionItem(subscriptionItemId: string, params?: CancelSubscriptionItemParams) {
41+
this.requireId(subscriptionItemId);
42+
return this.request<CommerceSubscriptionItem>({
43+
method: 'DELETE',
44+
path: joinPaths(basePath, 'subscription_items', subscriptionItemId),
45+
queryParams: params,
46+
});
47+
}
2648
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Feature } from './Feature';
22
import type { CommercePlanJSON } from './JSON';
33

4-
type CommerceMoneyAmount = {
4+
export type CommerceMoneyAmount = {
55
amount: number;
66
amountFormatted: string;
77
currency: string;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { CommerceMoneyAmountJSON } from '@clerk/types';
2+
3+
import { type CommerceMoneyAmount, CommercePlan } from './CommercePlan';
4+
import type { CommerceSubscriptionItemJSON } from './JSON';
5+
6+
/**
7+
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
8+
* It is advised to pin the SDK version to avoid breaking changes.
9+
*/
10+
export class CommerceSubscriptionItem {
11+
constructor(
12+
/**
13+
* The unique identifier for the subscription item.
14+
*/
15+
readonly id: string,
16+
/**
17+
* The status of the subscription item.
18+
*/
19+
readonly status: CommerceSubscriptionItemJSON['status'],
20+
/**
21+
* The plan period for the subscription item.
22+
*/
23+
readonly planPeriod: 'month' | 'annual',
24+
/**
25+
* The start of the current period.
26+
*/
27+
readonly periodStart: number,
28+
/**
29+
* The next payment information.
30+
*/
31+
readonly nextPayment: {
32+
amount: number;
33+
date: number;
34+
} | null,
35+
/**
36+
* The current amount for the subscription item.
37+
*/
38+
readonly amount: CommerceMoneyAmount | null | undefined,
39+
/**
40+
* The plan associated with this subscription item.
41+
*/
42+
readonly plan: CommercePlan,
43+
/**
44+
* The plan ID.
45+
*/
46+
readonly planId: string,
47+
/**
48+
* The end of the current period.
49+
*/
50+
readonly periodEnd?: number,
51+
/**
52+
* When the subscription item was canceled.
53+
*/
54+
readonly canceledAt?: number,
55+
/**
56+
* When the subscription item became past due.
57+
*/
58+
readonly pastDueAt?: number,
59+
/**
60+
* The lifetime amount paid for this subscription item.
61+
*/
62+
readonly lifetimePaid?: CommerceMoneyAmount | null,
63+
) {}
64+
65+
static fromJSON(data: CommerceSubscriptionItemJSON): CommerceSubscriptionItem {
66+
function formatAmountJSON(
67+
amount: CommerceMoneyAmountJSON | null | undefined,
68+
): CommerceMoneyAmount | null | undefined;
69+
function formatAmountJSON(
70+
amount: CommerceMoneyAmountJSON | null | undefined,
71+
): CommerceMoneyAmount | null | undefined {
72+
if (!amount) {
73+
return amount;
74+
}
75+
76+
return {
77+
amount: amount.amount,
78+
amountFormatted: amount.amount_formatted,
79+
currency: amount.currency,
80+
currencySymbol: amount.currency_symbol,
81+
};
82+
}
83+
84+
return new CommerceSubscriptionItem(
85+
data.id,
86+
data.status,
87+
data.plan_period,
88+
data.period_start,
89+
data.next_payment,
90+
formatAmountJSON(data.amount),
91+
CommercePlan.fromJSON(data.plan),
92+
data.plan_id,
93+
data.period_end,
94+
data.canceled_at,
95+
data.past_due_at,
96+
formatAmountJSON(data.lifetime_paid),
97+
);
98+
}
99+
}

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 { CommerceSubscriptionItem } from './CommerceSubscriptionItem';
4142
import { Feature } from './Feature';
4243
import type { PaginatedResponseJSON } from './JSON';
4344
import { ObjectType } from './JSON';
@@ -183,6 +184,8 @@ function jsonToObject(item: any): any {
183184
return WaitlistEntry.fromJSON(item);
184185
case ObjectType.CommercePlan:
185186
return CommercePlan.fromJSON(item);
187+
case ObjectType.CommerceSubscriptionItem:
188+
return CommerceSubscriptionItem.fromJSON(item);
186189
case ObjectType.Feature:
187190
return Feature.fromJSON(item);
188191
default:

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,44 @@ export interface CommercePlanJSON extends ClerkResourceJSON {
843843
features: FeatureJSON[];
844844
}
845845

846+
type CommerceSubscriptionItemStatus =
847+
| 'abandoned'
848+
| 'active'
849+
| 'canceled'
850+
| 'ended'
851+
| 'expired'
852+
| 'incomplete'
853+
| 'past_due'
854+
| 'upcoming';
855+
856+
/**
857+
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
858+
* It is advised to pin the SDK version to avoid breaking changes.
859+
*/
846860
export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
847861
object: typeof ObjectType.CommerceSubscriptionItem;
848-
status: 'abandoned' | 'active' | 'canceled' | 'ended' | 'expired' | 'incomplete' | 'past_due' | 'upcoming';
862+
status: CommerceSubscriptionItemStatus;
863+
plan_period: 'month' | 'annual';
864+
period_start: number;
865+
period_end?: number;
866+
canceled_at?: number;
867+
past_due_at?: number;
868+
lifetime_paid: CommerceMoneyAmountJSON;
869+
next_payment: {
870+
amount: number;
871+
date: number;
872+
} | null;
873+
amount: CommerceMoneyAmountJSON | null;
874+
plan: CommercePlanJSON;
875+
plan_id: string;
876+
}
877+
878+
/**
879+
* Webhooks specific interface for CommerceSubscriptionItem.
880+
*/
881+
export interface CommerceSubscriptionItemWebhookEventJSON extends ClerkResourceJSON {
882+
object: typeof ObjectType.CommerceSubscriptionItem;
883+
status: CommerceSubscriptionItemStatus;
849884
credit: {
850885
amount: CommerceMoneyAmountJSON;
851886
cycle_days_remaining: number;
@@ -882,7 +917,10 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
882917
plan_id: string;
883918
}
884919

885-
export interface CommercePaymentAttemptJSON extends ClerkResourceJSON {
920+
/**
921+
* Webhooks specific interface for CommercePaymentAttempt.
922+
*/
923+
export interface CommercePaymentAttemptWebhookEventJSON extends ClerkResourceJSON {
886924
object: typeof ObjectType.CommercePaymentAttempt;
887925
instance_id: string;
888926
payment_id: string;
@@ -912,10 +950,13 @@ export interface CommercePaymentAttemptJSON extends ClerkResourceJSON {
912950
card_type?: string;
913951
last4?: string;
914952
};
915-
subscription_items: CommerceSubscriptionItemJSON[];
953+
subscription_items: CommerceSubscriptionItemWebhookEventJSON[];
916954
}
917955

918-
export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
956+
/**
957+
* Webhooks specific interface for CommerceSubscription.
958+
*/
959+
export interface CommerceSubscriptionWebhookEventJSON extends ClerkResourceJSON {
919960
object: typeof ObjectType.CommerceSubscription;
920961
status: 'abandoned' | 'active' | 'canceled' | 'ended' | 'expired' | 'incomplete' | 'past_due' | 'upcoming';
921962
active_at?: number;
@@ -928,7 +969,7 @@ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
928969
payer_id: string;
929970
payer: CommercePayerJSON;
930971
payment_source_id: string;
931-
items: CommerceSubscriptionItemJSON[];
972+
items: CommerceSubscriptionItemWebhookEventJSON[];
932973
}
933974

934975
export interface WebhooksSvixJSON {

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {
2-
CommercePaymentAttemptJSON,
3-
CommerceSubscriptionItemJSON,
4-
CommerceSubscriptionJSON,
2+
CommercePaymentAttemptWebhookEventJSON,
3+
CommerceSubscriptionItemWebhookEventJSON,
4+
CommerceSubscriptionWebhookEventJSON,
55
DeletedObjectJSON,
66
EmailJSON,
77
OrganizationDomainJSON,
@@ -67,12 +67,12 @@ export type WaitlistEntryWebhookEvent = Webhook<'waitlistEntry.created' | 'waitl
6767

6868
export type CommercePaymentAttemptWebhookEvent = Webhook<
6969
'paymentAttempt.created' | 'paymentAttempt.updated',
70-
CommercePaymentAttemptJSON
70+
CommercePaymentAttemptWebhookEventJSON
7171
>;
7272

7373
export type CommerceSubscriptionWebhookEvent = Webhook<
7474
'subscription.created' | 'subscription.updated' | 'subscription.active' | 'subscription.past_due',
75-
CommerceSubscriptionJSON
75+
CommerceSubscriptionWebhookEventJSON
7676
>;
7777

7878
export type CommerceSubscriptionItemWebhookEvent = Webhook<
@@ -85,7 +85,7 @@ export type CommerceSubscriptionItemWebhookEvent = Webhook<
8585
| 'subscriptionItem.abandoned'
8686
| 'subscriptionItem.incomplete'
8787
| 'subscriptionItem.past_due',
88-
CommerceSubscriptionItemJSON
88+
CommerceSubscriptionItemWebhookEventJSON
8989
>;
9090

9191
export type WebhookEvent =

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 './CommerceSubscriptionItem';
6162

6263
export type {
6364
EmailWebhookEvent,

packages/backend/src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,8 @@ export type {
100100
PaginatedResponseJSON,
101101
TestingTokenJSON,
102102
WebhooksSvixJSON,
103-
CommercePayerJSON,
104103
CommercePlanJSON,
105104
CommerceSubscriptionItemJSON,
106-
CommercePaymentAttemptJSON,
107-
CommerceSubscriptionJSON,
108105
} from './api/resources/JSON';
109106

110107
/**
@@ -147,6 +144,7 @@ export type {
147144
User,
148145
TestingToken,
149146
CommercePlan,
147+
CommerceSubscriptionItem,
150148
} from './api/resources';
151149

152150
/**
@@ -166,6 +164,9 @@ export type {
166164
WaitlistEntryWebhookEvent,
167165
WebhookEvent,
168166
WebhookEventType,
167+
CommercePaymentAttemptWebhookEvent,
168+
CommerceSubscriptionWebhookEvent,
169+
CommerceSubscriptionItemWebhookEvent,
169170
} from './api/resources/Webhooks';
170171

171172
/**

0 commit comments

Comments
 (0)