-
Notifications
You must be signed in to change notification settings - Fork 377
feat(backend): Add cancelSubscriptionItem
to BillingApi
#6611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@clerk/backend': minor | ||
--- | ||
|
||
[Billing Beta] Add `cancelSubscriptionItem` to BillingApi. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,99 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type { CommerceMoneyAmountJSON } from '@clerk/types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { type CommerceMoneyAmount, CommercePlan } from './CommercePlan'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type { CommerceSubscriptionItemJSON } from './JSON'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @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 CommerceSubscriptionItem { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The unique identifier for the subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly id: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The status of the subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly status: CommerceSubscriptionItemJSON['status'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The plan period for the subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly planPeriod: 'month' | 'annual', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The start of the current period. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly periodStart: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The next payment information. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly nextPayment: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amount: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
date: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The current amount for the subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly amount: CommerceMoneyAmount | null | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Narrow the amount type to match JSON (avoid undefined) JSON shape defines Apply: - readonly amount: CommerceMoneyAmount | null | undefined,
+ readonly amount: CommerceMoneyAmount | null, 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The plan associated with this subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly plan: CommercePlan, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The plan ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly planId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The end of the current period. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly periodEnd?: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* When the subscription item was canceled. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly canceledAt?: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* When the subscription item became past due. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly pastDueAt?: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The lifetime amount paid for this subscription item. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
readonly lifetimePaid?: CommerceMoneyAmount | null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+60
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lifetimePaid should be required and non-null to match JSON
Apply: - readonly lifetimePaid?: CommerceMoneyAmount | null,
+ readonly lifetimePaid: CommerceMoneyAmount, 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
static fromJSON(data: CommerceSubscriptionItemJSON): CommerceSubscriptionItem { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function formatAmountJSON( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amount: CommerceMoneyAmountJSON | null | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): CommerceMoneyAmount | null | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function formatAmountJSON( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amount: CommerceMoneyAmountJSON | null | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): CommerceMoneyAmount | null | undefined { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!amount) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return amount; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amount: amount.amount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amountFormatted: amount.amount_formatted, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
currency: amount.currency, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
currencySymbol: amount.currency_symbol, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+66
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Simplify and type the amount formatter overloads precisely
Apply: - function formatAmountJSON(
- amount: CommerceMoneyAmountJSON | null | undefined,
- ): CommerceMoneyAmount | null | undefined;
- function formatAmountJSON(
- amount: CommerceMoneyAmountJSON | null | undefined,
- ): CommerceMoneyAmount | null | undefined {
- if (!amount) {
- return amount;
- }
-
- return {
- amount: amount.amount,
- amountFormatted: amount.amount_formatted,
- currency: amount.currency,
- currencySymbol: amount.currency_symbol,
- };
- }
+ function formatAmountJSON(amount: CommerceMoneyAmountJSON): CommerceMoneyAmount;
+ function formatAmountJSON(amount: CommerceMoneyAmountJSON | null): CommerceMoneyAmount | null;
+ function formatAmountJSON(amount: CommerceMoneyAmountJSON | null): CommerceMoneyAmount | null {
+ if (amount === null) {
+ return null;
+ }
+ return {
+ amount: amount.amount,
+ amountFormatted: amount.amount_formatted,
+ currency: amount.currency,
+ currencySymbol: amount.currency_symbol,
+ };
+ } And adjust the constructor call sites accordingly (type inference will keep 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return new CommerceSubscriptionItem( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.plan_period, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.period_start, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.next_payment, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
formatAmountJSON(data.amount), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CommercePlan.fromJSON(data.plan), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.plan_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.period_end, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.canceled_at, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data.past_due_at, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
formatAmountJSON(data.lifetime_paid), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,11 +100,8 @@ export type { | |
PaginatedResponseJSON, | ||
TestingTokenJSON, | ||
WebhooksSvixJSON, | ||
CommercePayerJSON, | ||
CommercePlanJSON, | ||
CommerceSubscriptionItemJSON, | ||
CommercePaymentAttemptJSON, | ||
CommerceSubscriptionJSON, | ||
} from './api/resources/JSON'; | ||
|
||
/** | ||
|
@@ -147,6 +144,7 @@ export type { | |
User, | ||
TestingToken, | ||
CommercePlan, | ||
CommerceSubscriptionItem, | ||
} from './api/resources'; | ||
|
||
/** | ||
|
@@ -166,6 +164,9 @@ export type { | |
WaitlistEntryWebhookEvent, | ||
WebhookEvent, | ||
WebhookEventType, | ||
CommercePaymentAttemptWebhookEvent, | ||
CommerceSubscriptionWebhookEvent, | ||
CommerceSubscriptionItemWebhookEvent, | ||
} from './api/resources/Webhooks'; | ||
Comment on lines
+167
to
170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainWebhook event type re-exports: verify event set coverage and SemVer impact
Proposed non-breaking aliases (deprecate old names, point to new webhook-specific JSONs): +/**
+ * @deprecated Use CommerceSubscriptionWebhookEventJSON instead. Will be removed in the next major.
+ */
+export type {
+ CommerceSubscriptionWebhookEventJSON as CommerceSubscriptionJSON,
+ CommercePaymentAttemptWebhookEventJSON as CommercePaymentAttemptJSON,
+} from './api/resources/JSON';
+
+/**
+ * @deprecated This type is still referenced by webhook JSON payloads, but we no longer recommend importing it from the backend package.
+ * Consider removing in the next major.
+ */
+export type { CommercePayerJSON } from './api/resources/JSON'; To check for potential breakages in consumers inside this repo: 🏁 Script executed: #!/bin/bash
# Find usages of removed type names across the monorepo
rg -nP -C2 '\b(CommerceSubscriptionJSON|CommercePaymentAttemptJSON|CommercePayerJSON)\b' Length of output: 5038 SemVer-breaking removal of JSON types – deprecate or bump major Confirmed that the following JSON interfaces are still referenced across the monorepo (in
Please address this before merging by either:
Locations to update (in
Suggested deprecation alias diff: // keep existing webhook re-exports
export {
CommercePaymentAttemptWebhookEvent,
CommerceSubscriptionWebhookEvent,
CommerceSubscriptionItemWebhookEvent,
} from './api/resources/Webhooks';
+/**
+ * @deprecated Use CommerceSubscriptionWebhookEventJSON instead. Will be removed in the next major.
+ */
+export type {
+ CommerceSubscriptionWebhookEventJSON as CommerceSubscriptionJSON,
+ CommercePaymentAttemptWebhookEventJSON as CommercePaymentAttemptJSON,
+} from './api/resources/JSON';
+
+/**
+ * @deprecated Use CommercePayerWebhookEventJSON instead. Will be removed in the next major.
+ */
+export type { CommercePayerJSON } from './api/resources/JSON';
🤖 Prompt for AI Agents
|
||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Explicit return type required for public API; also ensure
endNow
is serialized asend_now
.end_now
is likely the server-expected shape. Pass a mapped object.Apply this diff:
Optional: enrich JSDoc with param details and an example:
Also, please add tests that assert:
/commerce/subscription_items/{id}
end_now=true|false
CommerceSubscriptionItem
via DeserializerI can provide a test stub using the existing request mock harness.
🤖 Prompt for AI Agents