-
Notifications
You must be signed in to change notification settings - Fork 437
chore(backend): Add getOrganizationBillingSubscription to BillingApi
#6632
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
Changes from all commits
08ef122
0e94cc6
da68f92
4082bf3
3321c50
f6443b8
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 | ||
| --- | ||
|
|
||
| Add `getOrganizationBillingSubscription` to BillingApi. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { type CommerceMoneyAmount } from './CommercePlan'; | ||
| import { CommerceSubscriptionItem } from './CommerceSubscriptionItem'; | ||
| import type { CommerceSubscriptionJSON } 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 CommerceSubscription { | ||
| constructor( | ||
| /** | ||
| * The unique identifier for the commerce subscription. | ||
| */ | ||
| readonly id: string, | ||
| /** | ||
| * The current status of the subscription. | ||
| */ | ||
| readonly status: CommerceSubscriptionJSON['status'], | ||
| /** | ||
| * The ID of the payer for this subscription. | ||
| */ | ||
| readonly payerId: string, | ||
| /** | ||
| * Unix timestamp (milliseconds) of creation. | ||
| */ | ||
| readonly createdAt: number, | ||
| /** | ||
| * Unix timestamp (milliseconds) of last update. | ||
| */ | ||
| readonly updatedAt: number, | ||
| /** | ||
| * Unix timestamp (milliseconds) when the subscription became active. | ||
| */ | ||
| readonly activeAt: number | null, | ||
| /** | ||
| * Unix timestamp (milliseconds) when the subscription became past due. | ||
| */ | ||
| readonly pastDueAt: number | null, | ||
| /** | ||
| * Array of subscription items in this subscription. | ||
| */ | ||
| readonly subscriptionItems: CommerceSubscriptionItem[], | ||
| /** | ||
| * Information about the next scheduled payment. | ||
| */ | ||
| readonly nextPayment: { date: number; amount: CommerceMoneyAmount } | null, | ||
| /** | ||
| * Whether the payer is eligible for a free trial. | ||
| */ | ||
| readonly eligibleForFreeTrial: boolean, | ||
| ) {} | ||
|
|
||
| static fromJSON(data: CommerceSubscriptionJSON): CommerceSubscription { | ||
| const nextPayment = data.next_payment | ||
| ? { | ||
| date: data.next_payment.date, | ||
| amount: { | ||
| amount: data.next_payment.amount.amount, | ||
| amountFormatted: data.next_payment.amount.amount_formatted, | ||
| currency: data.next_payment.amount.currency, | ||
| currencySymbol: data.next_payment.amount.currency_symbol, | ||
| }, | ||
| } | ||
| : null; | ||
|
|
||
| return new CommerceSubscription( | ||
| data.id, | ||
| data.status, | ||
| data.payer_id, | ||
| data.created_at, | ||
| data.updated_at, | ||
| data.active_at ?? null, | ||
| data.past_due_at ?? null, | ||
| data.subscription_items.map(item => CommerceSubscriptionItem.fromJSON(item)), | ||
| nextPayment, | ||
| data.eligible_for_free_trial ?? false, | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -861,10 +861,15 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON { | |||||||||||||||||||||
| object: typeof ObjectType.CommerceSubscriptionItem; | ||||||||||||||||||||||
| status: CommerceSubscriptionItemStatus; | ||||||||||||||||||||||
| plan_period: 'month' | 'annual'; | ||||||||||||||||||||||
| payer_id: string; | ||||||||||||||||||||||
| period_start: number; | ||||||||||||||||||||||
| period_end?: number; | ||||||||||||||||||||||
| canceled_at?: number; | ||||||||||||||||||||||
| past_due_at?: number; | ||||||||||||||||||||||
| period_end: number | null; | ||||||||||||||||||||||
| is_free_trial?: boolean; | ||||||||||||||||||||||
| ended_at: number | null; | ||||||||||||||||||||||
| created_at: number; | ||||||||||||||||||||||
| updated_at: number; | ||||||||||||||||||||||
| canceled_at: number | null; | ||||||||||||||||||||||
| past_due_at: number | null; | ||||||||||||||||||||||
| lifetime_paid: CommerceMoneyAmountJSON; | ||||||||||||||||||||||
| next_payment: { | ||||||||||||||||||||||
| amount: number; | ||||||||||||||||||||||
|
|
@@ -972,6 +977,22 @@ export interface CommerceSubscriptionWebhookEventJSON extends ClerkResourceJSON | |||||||||||||||||||||
| items: CommerceSubscriptionItemWebhookEventJSON[]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export interface CommerceSubscriptionJSON extends ClerkResourceJSON { | ||||||||||||||||||||||
|
Contributor
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 Add comprehensive JSDoc for the new public API Per repo guidelines, all public APIs need JSDoc. Add the experimental notice and field-level docs for Apply this diff: - export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
+ /**
+ * @experimental This is an experimental API for the Billing feature that is available under a public beta,
+ * and the API is subject to change. Pin the SDK version to avoid breaking changes.
+ *
+ * Data model returned by `BillingApi.getOrganizationBillingSubscription`.
+ * - `subscription_items`: list of items in this subscription.
+ * - `next_payment`: next scheduled charge for the subscription, or null if none.
+ */
+ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| object: typeof ObjectType.CommerceSubscription; | ||||||||||||||||||||||
| status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete'; | ||||||||||||||||||||||
| payer_id: string; | ||||||||||||||||||||||
| created_at: number; | ||||||||||||||||||||||
| updated_at: number; | ||||||||||||||||||||||
| active_at: number | null; | ||||||||||||||||||||||
| past_due_at: number | null; | ||||||||||||||||||||||
| subscription_items: CommerceSubscriptionItemJSON[]; | ||||||||||||||||||||||
| next_payment?: { | ||||||||||||||||||||||
| date: number; | ||||||||||||||||||||||
| amount: CommerceMoneyAmountJSON; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| eligible_for_free_trial?: boolean; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+980
to
+994
Contributor
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 chainPublic contract mismatch with packages/types: high risk of consumer breakage This CommerceSubscriptionJSON differs from packages/types/src/json.ts in multiple ways:
Two safe paths: Option A — Align with SDK shape (preferred for a public surface): export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
object: typeof ObjectType.CommerceSubscription;
- status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
- payer_id: string;
+ status: 'active' | 'past_due';
created_at: number;
- updated_at: number;
- active_at: number | null;
+ active_at: number;
+ updated_at: number | null;
past_due_at: number | null;
- subscription_items: CommerceSubscriptionItemJSON[];
+ subscription_items: CommerceSubscriptionItemJSON[] | null;
next_payment?: {
date: number;
amount: CommerceMoneyAmountJSON;
};
eligible_for_free_trial?: boolean;
}Additionally, update CommerceSubscription.fromJSON to handle possibly-null subscription_items and nullable updated_at; and drop payerId unless it exists at another level. Option B — Keep extended backend shape but scope it:
Given the scope of impact, please choose one path before merging. I can provide a follow-up patch for either approach, including the mapper changes in CommerceSubscription.ts. 🏁 Script executed: #!/bin/bash
set -e
# Locate json.ts in packages/types/src
json_file=$(fd -t f json.ts packages/types/src | head -n1)
echo "Using file: $json_file"
# Show the CommerceSubscriptionJSON interface and its key fields
rg -n 'interface CommerceSubscriptionJSON' -C10 "$json_file"
rg -n 'status' -C2 "$json_file"
rg -n 'payer_id' -C2 "$json_file"
rg -n 'active_at' -C2 "$json_file"
rg -n 'updated_at' -C2 "$json_file"
rg -n 'subscription_items' -C2 "$json_file"Length of output: 4023 🏁 Script executed: #!/bin/bash
set -e
types_file="packages/types/src/json.ts"
echo "SDK JSON File: $types_file"
# Extract the CommerceSubscriptionJSON block
start=$(rg -n "export interface CommerceSubscriptionJSON" -n "$types_file" | cut -d: -f1)
# Capture through the matching closing brace (assuming no nested braces inside)
relative_end=$(tail -n +$start "$types_file" | sed -n '1,/^}/p' | tail -n1 | grep -n '^}' | cut -d: -f1)
end=$((start + relative_end - 1))
echo "--- CommerceSubscriptionJSON Definition ---"
sed -n "${start},${end}p" "$types_file"
echo
echo "--- Key Property Definitions ---"
echo "status:"
rg -n "status:" -C2 "$types_file" | sed -n '1,5p'
echo
echo "updated_at:"
rg -n "updated_at:" -C2 "$types_file" | sed -n '1,5p'
echo
echo "subscription_items:"
rg -n "subscription_items:" -C2 "$types_file" | sed -n '1,5p'Length of output: 1458 🏁 Script executed: #!/bin/bash
set -e
# Print lines 970-1040 of the backend JSON file to inspect CommerceSubscriptionJSON
sed -n '970,1040p' packages/backend/src/api/resources/JSON.tsLength of output: 857 Public JSON contract mismatch in CommerceSubscriptionJSON (packages/backend/src/api/resources/JSON.ts) The • status Two paths forward:
Given the high risk of breaking changes, please choose one approach before merging. I can follow up with a patch for either path. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export interface WebhooksSvixJSON { | ||||||||||||||||||||||
| svix_url: string; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
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.
💡 Verification agent
🧩 Analysis chain
Add explicit return type and complete JSDoc for the public API
Per guidelines, public APIs should declare explicit return types and comprehensive JSDoc.
Apply this diff within the method block:
/** * @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. + * @param organizationId - The target organization ID (e.g. "org_..."). + * @returns The organization's billing subscription resource. + * @throws ClerkAPIResponseError If the organization does not exist or billing is not enabled. */ - public async getOrganizationBillingSubscription(organizationId: string) { + public async getOrganizationBillingSubscription(organizationId: string): Promise<CommerceSubscription> { this.requireId(organizationId); return this.request<CommerceSubscription>({ method: 'GET', path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'), }); }I can add an integration test stub that mocks
AbstractAPI.requestto verify the shape and deserialization path—want me to push that?🏁 Script executed:
Length of output: 11395
Add explicit return type and complete JSDoc for
getOrganizationBillingSubscriptionPublic SDK methods must declare explicit return types and comprehensive JSDoc for parameters, return values, and thrown errors per our API guidelines.
– In
packages/backend/src/api/endpoints/BillingApi.ts, update the method signature:/** * @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. + * @param organizationId – The target organization’s ID (e.g. "org_…"). + * @returns The organization’s billing subscription resource. + * @throws ClerkAPIResponseError If the organization does not exist or billing is not enabled. */ - public async getOrganizationBillingSubscription(organizationId: string) { + public async getOrganizationBillingSubscription( + organizationId: string + ): Promise<CommerceSubscription> { this.requireId(organizationId); return this.request<CommerceSubscription>({ method: 'GET', path: joinPaths(organizationBasePath, organizationId, 'billing', 'subscription'), }); }• Please also verify that the
billing/subscriptionendpoint path aligns exactly with our server’s API specification—I wasn’t able to locate a matching route definition in the repo.• Let me know if you’d like me to add an integration‐test stub that mocks
AbstractAPI.requestto validate the response shape and deserialization path.📝 Committable suggestion
🤖 Prompt for AI Agents